• <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>
            posts - 4,  comments - 27,  trackbacks - 0
                前些天花了很多時(shí)間寫這樣一個(gè)軟鍵盤,效果是顯示一個(gè)與鍵盤外觀相似的視圖,通過(guò)鼠標(biāo)單擊像活動(dòng)窗口發(fā)送虛擬的鍵盤消息。目標(biāo)是實(shí)現(xiàn)像windows自帶的軟鍵盤osk相似。
                看似很簡(jiǎn)單的工作,設(shè)計(jì)中卻遇到了很多困難。
                困難一:鍵盤按鍵分類
                    鍵盤按鍵有很多種分類方法。
                    第一種:按顯示分類。按住shift鍵,字母鍵、符號(hào)鍵顯示上面的字符;按下caps lock鍵,字母鍵切換為大寫字母。
                    第二種:按功能分類。大體有可顯示字符類、控制類。控制類包括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)于單刀雙擲開關(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)組總個(gè)數(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;

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

            //小鍵盤數(shù)字表
            extern LPCSTR NumPadTable1[];    //數(shù)字
            extern LPCSTR NumPadTable2[];    //光標(biāo)控制
            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)生很多種不同的界面(雖然仍然是代碼級(jí)別的,畢竟邁出了第一步,今后還會(huì)嘗試改成xml配置)。
                    言歸正傳,這樣的設(shè)計(jì)分離了按鍵與顯示,可配置能力大大加強(qiáng)。但仍然存在第二個(gè)大問(wèn)題。
                問(wèn)題二:輸入焦點(diǎn)的確定
                    方案一:現(xiàn)在只要在網(wǎng)上搜索“虛擬鍵盤”,能夠搜到一大溜的源代碼,但只可惜全是同一份拷貝,而且存在一點(diǎn)小錯(cuò)誤。他的解決方案是:利用 PreTranslateMessage,在底層調(diào)用它之前,前臺(tái)窗口仍然沒(méi)有改變,此時(shí)是獲得前一個(gè)前臺(tái)窗口的好時(shí)機(jī),獲得后保存,并將使用 AttachThreadInput將當(dāng)前線程綁定活動(dòng)窗口的消息隊(duì)列,然后在單擊虛擬鍵盤時(shí)使用SetFocus將保存的窗口設(shè)為焦點(diǎn)(源代碼中同時(shí)使用了SetForgroundWindow和SetFocus,這是失效的原因),然后發(fā)送虛擬按鍵。

                    方案二:其實(shí)有更簡(jiǎn)便的方法。設(shè)置主窗口屬性為WM_ES_NOACTIVATE,這樣窗口就不會(huì)成為前臺(tái)窗口,不管如何發(fā)送鍵盤消息,擁有焦點(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似乎有意跟我們開玩笑,必須單擊兩次才能響應(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。
                    這樣,如果虛擬得比較徹底,在虛擬鍵盤內(nèi)部可以輕易地實(shí)現(xiàn)狀態(tài)的記憶,并且可以獲得足夠的信息。對(duì)于顯示、控制都非常方便。

                這只是第一個(gè)版本,還有很多問(wèn)題需要解決。
                待解決問(wèn)題一:xml配置動(dòng)態(tài)配置鍵盤,及動(dòng)態(tài)更換顯示效果。
                待解決問(wèn)題二:同步物理鍵盤。
                待解決問(wèn)題三:更深層次,防止鍵盤消息被hook,初步認(rèn)識(shí),似乎可以使用剪貼板。
               【源代碼1.2版本:http://www.shnenglu.com/Files/yefeng/VirtualKeyboard1.2.rar
            posted on 2009-10-18 23:19 夜風(fēng) 閱讀(4714) 評(píng)論(6)  編輯 收藏 引用 所屬分類: C/C++技術(shù)windows程序設(shè)計(jì)

            FeedBack:
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-18 23:59 | expter
            學(xué)習(xí)。。。  回復(fù)  更多評(píng)論
              
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-19 00:11 | OwnWaterloo
            1.要搜kbcwait4ibe,不要搜"虛擬鍵盤"。
            2.以下劃線開始的標(biāo)識(shí)符是C/C++語(yǔ)言所保留的啊,同學(xué)們……
              回復(fù)  更多評(píng)論
              
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-19 00:20 | 夜風(fēng)
            @OwnWaterloo
            1.kbcwait4ibe是驅(qū)動(dòng)級(jí)別的哦,正打算開始研究驅(qū)動(dòng)呢。。。
            2.哦,是的,倒是沒(méi)注意這個(gè)。。。但這命名還真是個(gè)傷腦筋的問(wèn)題呢!  回復(fù)  更多評(píng)論
              
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-19 14:51 | 淘寶皇冠店
            學(xué)習(xí)技術(shù)!!  回復(fù)  更多評(píng)論
              
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-19 20:56 | zhaoyg
            學(xué)習(xí)  回復(fù)  更多評(píng)論
              
            # re: 虛擬鍵盤(軟鍵盤)設(shè)計(jì)要點(diǎn)
            2009-10-22 09:29 | 李佳
            寫的挺好的 支持原創(chuàng) 樓主費(fèi)心了   回復(fù)  更多評(píng)論
              
            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            常用鏈接

            留言簿(1)

            隨筆分類(7)

            隨筆檔案(4)

            文章分類

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            国产亚洲精品久久久久秋霞| 久久久国产精品| 狠狠色丁香久久婷婷综合图片| 亚洲狠狠综合久久| 伊人久久免费视频| 青青青伊人色综合久久| 蜜桃麻豆www久久| 久久香蕉国产线看观看乱码| 国产成人综合久久综合| 国内精品久久久久影院免费| 天天综合久久久网| 精品无码人妻久久久久久| 久久久久久噜噜精品免费直播| 色婷婷久久久SWAG精品| 国产精品久久久久蜜芽| 日韩久久久久久中文人妻| 久久66热人妻偷产精品9| 青青国产成人久久91网| 亚洲国产精品无码久久青草| 丁香色欲久久久久久综合网| 久久精品中文字幕无码绿巨人| 99精品久久精品一区二区| 国产精品内射久久久久欢欢| 欧美一级久久久久久久大片| 亚洲AV无码1区2区久久| 99久久成人18免费网站| 老男人久久青草av高清| 精品久久无码中文字幕| 久久久久亚洲AV成人网人人软件| 久久精品国产亚洲AV久| 潮喷大喷水系列无码久久精品 | 97久久精品国产精品青草| 91久久福利国产成人精品| 人妻无码精品久久亚瑟影视 | 2021国产精品久久精品| 97久久精品无码一区二区天美| 久久男人中文字幕资源站| 久久久久久毛片免费播放| 久久青青国产| 国产高潮国产高潮久久久91 | 久久亚洲国产成人影院|