• <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
                前些天花了很多時間寫這樣一個軟鍵盤,效果是顯示一個與鍵盤外觀相似的視圖,通過鼠標單擊像活動窗口發送虛擬的鍵盤消息。目標是實現像windows自帶的軟鍵盤osk相似。
                看似很簡單的工作,設計中卻遇到了很多困難。
                困難一:鍵盤按鍵分類
                    鍵盤按鍵有很多種分類方法。
                    第一種:按顯示分類。按住shift鍵,字母鍵、符號鍵顯示上面的字符;按下caps lock鍵,字母鍵切換為大寫字母。
                    第二種:按功能分類。大體有可顯示字符類、控制類。控制類包括shift,ctrl等。
                    為了解決可變的顯示問題,采用了一個自我感覺非常好的解決方案:字符集、鍵集相互獨立。如此一來,只要總體按照功能分類,通過特定功能的按鍵控制有效字符集即可,也就是說,對普通按鍵來說,它只負責到指定的字符集中去取對應序號的字符即可。
            //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;
            };

            //相當于單刀雙擲開關組
            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;    //開關組總個數
                Switch* pGroup;    //開關組
            };

            //
            //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;
            }
                    以上取開關的索引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;

            //小鍵盤數字表
            extern LPCSTR NumPadTable1[];    //數字
            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;    //類內偏移
                RECT rt;    //位置
                BYTE vk;    //虛擬碼
            };

            extern KeyConfig kcs[];
            extern const int kcSize;
            extern const SIZE kbSize;
                    第一次這樣寫代碼,寫完發現這樣極大地提高了靈活性,只要在配置文件config.cpp中修改,就可以產生很多種不同的界面(雖然仍然是代碼級別的,畢竟邁出了第一步,今后還會嘗試改成xml配置)。
                    言歸正傳,這樣的設計分離了按鍵與顯示,可配置能力大大加強。但仍然存在第二個大問題。
                問題二:輸入焦點的確定
                    方案一:現在只要在網上搜索“虛擬鍵盤”,能夠搜到一大溜的源代碼,但只可惜全是同一份拷貝,而且存在一點小錯誤。他的解決方案是:利用 PreTranslateMessage,在底層調用它之前,前臺窗口仍然沒有改變,此時是獲得前一個前臺窗口的好時機,獲得后保存,并將使用 AttachThreadInput將當前線程綁定活動窗口的消息隊列,然后在單擊虛擬鍵盤時使用SetFocus將保存的窗口設為焦點(源代碼中同時使用了SetForgroundWindow和SetFocus,這是失效的原因),然后發送虛擬按鍵。

                    方案二:其實有更簡便的方法。設置主窗口屬性為WM_ES_NOACTIVATE,這樣窗口就不會成為前臺窗口,不管如何發送鍵盤消息,擁有焦點的窗口總會收到。但此時仍然存在問題。當移動窗口時,效果不大順暢,而且沒辦法響應菜單命令,那是因為該窗口始終不是前臺窗口造成的。解決方法就是在單擊標題欄時,成為前臺窗口,釋放是歸還前臺。

            void CMainFrame::OnNcLButtonDown(UINT nHitTest, CPoint point)
            {
                
            if(m_hForground == NULL)
                {
                    m_hForground 
            = ::GetForegroundWindow();
                    ModifyStyleEx(WS_EX_NOACTIVATE,
            0);
                    SetForegroundWindow();
                }
                CFrameWnd::OnNcLButtonDown(nHitTest, point);
            }
                            但是,如果想當然歸還前臺使用WM_NCLBUTTONUP消息的話,就要讓你失望了,windows似乎有意跟我們開玩笑,必須單擊兩次才能響應這個消息。沒辦法,于是嘗試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);
            }
                    問題到此為止,現在說說一點小小的發現。
                    原本以為一般的按鍵就兩種狀態,通過down、up改變,如果用方波描述,down就是下降沿觸發,up是上升沿觸發。也曾了解,像shift這樣的按鍵會很復雜,存在多個狀態。后來測試發現,shift并非一個特例,所有的按鍵都有4個狀態,通過down、up改變狀態。只是不同按鍵對狀態的關注點不同。
                    可以做這樣一個測試,用GetKeyboardState得到各個虛擬碼對應的按鍵狀態。最高位為1時表示鍵被按下,最高位為1時,如果是lock鍵則表示被鎖住,對于其他鍵,各有各的作用。
                    比如一個鍵,用2位的二進制數表示這些狀態,設初始狀態為10,經過down后,變為01,經過up后,變為11,再經過down后,變為00,再經過up后,變為10,如此四個狀態經過down、up實現了周期性的狀態裝換。大體符合這樣的規律:
                        10-(down xor 11)->01->(up xor 10)->11-(down xor 11)->00(up xor 10)->10。
                    這樣,如果虛擬得比較徹底,在虛擬鍵盤內部可以輕易地實現狀態的記憶,并且可以獲得足夠的信息。對于顯示、控制都非常方便。

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

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

            常用鏈接

            留言簿(1)

            隨筆分類(7)

            隨筆檔案(4)

            文章分類

            最新評論

            閱讀排行榜

            評論排行榜

            久久精品国产亚洲av麻豆蜜芽 | 久久久久久久久久久| 久久久受www免费人成| 97精品依人久久久大香线蕉97 | 中文字幕久久精品无码| 97久久天天综合色天天综合色hd | 国产三级久久久精品麻豆三级| 精品久久久久久99人妻| 久久亚洲AV成人无码国产| 中文成人久久久久影院免费观看| 久久精品成人国产午夜| 久久99精品久久久久久动态图| 日本久久久久久中文字幕| 久久A级毛片免费观看| 乱亲女H秽乱长久久久| 久久精品亚洲乱码伦伦中文| 夜夜亚洲天天久久| 99久久无色码中文字幕| 日韩精品无码久久一区二区三| 91久久精品电影| 久久综合亚洲色一区二区三区| 久久只有这里有精品4| 精品久久久久成人码免费动漫| 88久久精品无码一区二区毛片 | 色综合久久无码中文字幕| 久久久久久精品无码人妻| www.久久精品| 看久久久久久a级毛片| 理论片午午伦夜理片久久 | 久久青青草原亚洲av无码app| 久久影院亚洲一区| 久久99久久无码毛片一区二区 | 久久WWW免费人成—看片| 久久美女人爽女人爽| 77777亚洲午夜久久多喷| 精品久久一区二区| 国产亚洲美女精品久久久久狼| 久久亚洲私人国产精品vA| 久久久久AV综合网成人| 国内精品九九久久久精品| 久久精品国产99久久久|