• <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.¢%

            像打了激速一樣,以四倍的速度運轉(zhuǎn),開心的工作
            簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            虛擬鍵盤(軟鍵盤)設(shè)計要點

            Posted on 2009-10-19 14:27 S.l.e!ep.¢% 閱讀(3686) 評論(5)  編輯 收藏 引用 所屬分類: VC
            ??? 前些天花了很多時間寫這樣一個軟鍵盤,效果是顯示一個與鍵盤外觀相似的視圖,通過鼠標單擊像活動窗口發(fā)送虛擬的鍵盤消息。目標是實現(xiàn)像windows自帶的軟鍵盤osk相似。
            ??? 看似很簡單的工作,設(shè)計中卻遇到了很多困難。
            ??? 困難一:鍵盤按鍵分類
            ??????? 鍵盤按鍵有很多種分類方法。
            ??????? 第一種:按顯示分類。按住shift鍵,字母鍵、符號鍵顯示上面的字符;按下caps lock鍵,字母鍵切換為大寫字母。
            ??????? 第二種:按功能分類。大體有可顯示字符類、控制類。控制類包括shift,ctrl等。
            ??????? 為了解決可變的顯示問題,采用了一個自我感覺非常好的解決方案:字符集、鍵集相互獨立。如此一來,只要總體按照功能分類,通過特定功能的按鍵控制有效字符集即可,也就是說,對普通按鍵來說,它只負責(zé)到指定的字符集中去取對應(yīng)序號的字符即可。
            //LabelSet.h
            #pragma?once

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

            ????
            ~LabelSet();

            protected:
            ????LabelSet(){}

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

            //相當(dāng)于單刀雙擲開關(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;????//開關(guān)組總個數(shù)
            ????Switch*?pGroup;????//開關(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;
            }
            ??????? 以上取開關(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[];????//小寫
            extern?LPCSTR?AlphaTable2[];????//大寫
            extern?const?int?AlphaTableSize;

            //符號串表
            extern?LPCSTR?SymbolTable1[];????//
            extern?LPCSTR?SymbolTable2[];????//
            extern?const?int?SymbolTableSize;

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

            //主鍵盤單顯
            extern?LPCSTR?MainTable[];
            extern?const?int?MainTableSize;

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

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

            Feedback

            # re: 虛擬鍵盤(軟鍵盤)設(shè)計要點   回復(fù)  更多評論   

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

            # re: 虛擬鍵盤(軟鍵盤)設(shè)計要點   回復(fù)  更多評論   

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

            # re: 虛擬鍵盤(軟鍵盤)設(shè)計要點   回復(fù)  更多評論   

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

            # re: 虛擬鍵盤(軟鍵盤)設(shè)計要點   回復(fù)  更多評論   

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

            # re: 虛擬鍵盤(軟鍵盤)設(shè)計要點   回復(fù)  更多評論   

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

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

            非常感謝
            精品熟女少妇av免费久久| 狠狠色丁香久久综合婷婷| 色综合久久中文字幕综合网| 国产免费久久精品丫丫| 久久久精品日本一区二区三区| 久久婷婷五月综合97色直播| 日韩美女18网站久久精品| 亚洲国产成人精品女人久久久 | 99久久无色码中文字幕人妻| 久久人与动人物a级毛片| 热re99久久精品国99热| 精品国产乱码久久久久久郑州公司| 久久国产热精品波多野结衣AV| 国内精品久久国产大陆| 久久成人18免费网站| 伊人久久大香线蕉综合热线| 亚洲国产另类久久久精品| 国产精品美女久久久| 久久电影网| 精品人妻伦九区久久AAA片69| 久久久女人与动物群交毛片| 香港aa三级久久三级| 精品无码人妻久久久久久| 99久久这里只精品国产免费| 国内精品九九久久久精品| 很黄很污的网站久久mimi色| 热99RE久久精品这里都是精品免费 | 四虎影视久久久免费| 亚洲精品午夜国产VA久久成人| 久久精品国产亚洲AV嫖农村妇女| 久久久综合九色合综国产| 欧美国产精品久久高清| 亚洲AV日韩精品久久久久久久| 久久午夜电影网| 久久婷婷人人澡人人爽人人爱| 国产一区二区三区久久| 天天做夜夜做久久做狠狠| 99久久精品毛片免费播放| 亚洲午夜福利精品久久| 狠狠色丁香婷婷综合久久来| 久久久久这里只有精品|