• <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
                前些天花了很多時間寫這樣一個軟鍵盤,效果是顯示一個與鍵盤外觀相似的視圖,通過鼠標(biāo)單擊像活動窗口發(fā)送虛擬的鍵盤消息。目標(biāo)是實(shí)現(xiàn)像windows自帶的軟鍵盤osk相似。
                看似很簡單的工作,設(shè)計中卻遇到了很多困難。
                困難一:鍵盤按鍵分類
                    鍵盤按鍵有很多種分類方法。
                    第一種:按顯示分類。按住shift鍵,字母鍵、符號鍵顯示上面的字符;按下caps lock鍵,字母鍵切換為大寫字母。
                    第二種:按功能分類。大體有可顯示字符類、控制類。控制類包括shift,ctrl等。
                    為了解決可變的顯示問題,采用了一個自我感覺非常好的解決方案:字符集、鍵集相互獨(dú)立。如此一來,只要總體按照功能分類,通過特定功能的按鍵控制有效字符集即可,也就是說,對普通按鍵來說,它只負(fù)責(zé)到指定的字符集中去取對應(yīng)序號的字符即可。
            //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)組總個數(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[];    //光標(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)生很多種不同的界面(雖然仍然是代碼級別的,畢竟邁出了第一步,今后還會嘗試改成xml配置)。
                    言歸正傳,這樣的設(shè)計分離了按鍵與顯示,可配置能力大大加強(qiáng)。但仍然存在第二個大問題。
                問題二:輸入焦點(diǎn)的確定
                    方案一:現(xiàn)在只要在網(wǎng)上搜索“虛擬鍵盤”,能夠搜到一大溜的源代碼,但只可惜全是同一份拷貝,而且存在一點(diǎn)小錯誤。他的解決方案是:利用 PreTranslateMessage,在底層調(diào)用它之前,前臺窗口仍然沒有改變,此時是獲得前一個前臺窗口的好時機(jī),獲得后保存,并將使用 AttachThreadInput將當(dāng)前線程綁定活動窗口的消息隊列,然后在單擊虛擬鍵盤時使用SetFocus將保存的窗口設(shè)為焦點(diǎn)(源代碼中同時使用了SetForgroundWindow和SetFocus,這是失效的原因),然后發(fā)送虛擬按鍵。

                    方案二:其實(shí)有更簡便的方法。設(shè)置主窗口屬性為WM_ES_NOACTIVATE,這樣窗口就不會成為前臺窗口,不管如何發(fā)送鍵盤消息,擁有焦點(diǎn)的窗口總會收到。但此時仍然存在問題。當(dāng)移動窗口時,效果不大順暢,而且沒辦法響應(yīng)菜單命令,那是因?yàn)樵摯翱谑冀K不是前臺窗口造成的。解決方法就是在單擊標(biāo)題欄時,成為前臺窗口,釋放是歸還前臺。

            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)在說說一點(diǎ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)注點(diǎn)不同。
                    可以做這樣一個測試,用GetKeyboardState得到各個虛擬碼對應(yīng)的按鍵狀態(tài)。最高位為1時表示鍵被按下,最高位為1時,如果是lock鍵則表示被鎖住,對于其他鍵,各有各的作用。
                    比如一個鍵,用2位的二進(jìn)制數(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實(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)的記憶,并且可以獲得足夠的信息。對于顯示、控制都非常方便。

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

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

            常用鏈接

            留言簿(1)

            隨筆分類(7)

            隨筆檔案(4)

            文章分類

            最新評論

            閱讀排行榜

            評論排行榜

            久久久久亚洲AV无码观看| 超级碰久久免费公开视频| 久久国产综合精品五月天| 久久免费小视频| 久久狠狠色狠狠色综合| 国产成人精品白浆久久69| 国内精品久久久久影院一蜜桃| 亚洲国产精品久久久天堂| 日本强好片久久久久久AAA | 天天综合久久一二三区| 久久久久综合国产欧美一区二区| 久久久久国产一区二区| 中文字幕久久亚洲一区| 2021国产精品久久精品| 久久久无码一区二区三区| 国产精品欧美久久久天天影视| 日本免费久久久久久久网站| 国产精品熟女福利久久AV| 老司机午夜网站国内精品久久久久久久久 | 亚洲精品国产成人99久久| 精品久久久久久无码免费| 国产香蕉久久精品综合网| 色诱久久久久综合网ywww| AA级片免费看视频久久| 久久婷婷人人澡人人爽人人爱| 日韩精品久久无码中文字幕| 丁香久久婷婷国产午夜视频| 欧美精品九九99久久在观看| 国产V亚洲V天堂无码久久久| 久久久久18| A狠狠久久蜜臀婷色中文网| 欧美精品丝袜久久久中文字幕 | 亚洲香蕉网久久综合影视| 国产精品久久久久乳精品爆| 久久九九兔免费精品6| 亚洲国产精品久久久久婷婷老年| 久久精品国产免费观看| 国产精品无码久久四虎| 伊人色综合久久天天人手人婷| 91久久香蕉国产熟女线看| 久久综合给合久久国产免费 |