線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:2163
推到 Plurk!
推到 Facebook!

Auto-Complete edit controls

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2003-01-06 15:31:39 IP:61.218.xxx.xxx 未訂閱

Auto-Complete edit controls

by Damon Chandler 資料來源:http://www.bridgespublishing.com Several months ago, I demonstrated how to create a generic popup form for use as the drop-down portion of a combo box type control. This month, I’ll show you how to use a similar approach to create an autocomplete edit control, such as that depicted in Figure A. Figure A An AutoComplete edit control. There are two main things that are required to create an auto-complete edit control: (1) the edit control itself, and (2) the list box that serves as the drop-down portion. The second task is the easier part, so I’ll cover that first. A pop-up list box control In the article “Custom Pop-up Controls” (see the May 2001 issue) I discussed the steps required to make a pop-up type form. Whereas in that article, I used a form to provide a level of generality, here we don’t need to use an extra form because the control that’s going to pop up will always be a list box. Accordingly, Listing A contains the declaration of a TListBox descendant class, called TPopupListBox. Setting up the list box Recall that there are two steps required to make a pop-up type control: (1) you need to set the proper window styles; and (2) you need to make the control a child of the desktop window. The window styles can be specified from within the CreateParams() method, like so:
    void __fastcall TPopupListBox::      CreateParams(TCreateParams& Prms)    {      TListBox::CreateParams(Prms);      Prms.Style |= WS_BORDER;      Prms.ExStyle |= WS_EX_PALETTEWINDOW;      Prms.WindowClass.style |= CS_SAVEBITS;    }     
The WS_BORDER style simply provides the list box with a thin black border. The WS_EX_PALETTEWINDOW style makes the list box top-level and hidden from Windows’ task list. The CS_SAVEBITS class style, which isn’t strictly required, instructs Windows to screen capture the area over which the list box will appear; this way, when the list box is hidden, Windows can quickly restore the area without generating WM_PAINT messages. The SetParent() and GetDesktopWindow() API functions are used to make the list box a child of the desktop window. Because this needs to be done after the TListBox object’s underlying window is created, we’ll call these functions from within the CreateWnd() method (after calling the parent class’s CreateWnd() method), like so:
void __fastcall      TPopupListBox::CreateWnd()    {      TListBox::CreateWnd();      ::SetParent(        WindowHandle, GetDesktopWindow());    }     
Handling mouse events The next step is to add code to synchronize the selected item with the position of the mouse cursor, and code to update the associated edit control after the user selects an item. These tasks can be performed, respectively, from within the MouseMove() and MouseUp() methods:
    void __fastcall TPopupListBox::      MouseMove(TShiftState Shift,      int X, int Y)    {      TListBox::MouseMove(Shift, X, Y);           // find the index of the item over      // which the mouse cursor is located      const int index =        ItemAtPos(Point(X, Y), false);      if (index != -1) {        // select that item        ItemIndex = index;      }    }         void __fastcall TPopupListBox::MouseUp(      TMouseButton Button,       TShiftState Shift, int X, int Y)    {      TListBox::MouseUp(Button, Shift, X,Y);           // find the index of the item over      // which the mouse cursor is located      const int index =        ItemAtPos(Point(X, Y), true);      if (index != -1) {        if (Edit_ != NULL) {          // update the edit control          Edit_->Text =            Items->Strings[index];          Edit_->SelectAll();        }        // hide the popup list        Hide();      }    }     
Both of these methods use the TListBox::ItemAtPos() method to determine the index of the list box item that’s located under the mouse cursor. In the MouseMove() method, the returned index is assigned to the list box’s ItemIndex property to set the selected item. In the MouseUp() method, the returned index specifies the item that the user clicked; the text of this item is then assigned to the list box’s associated edit control (held in the private TPopupListBox::Edit_ member). That’s it for the popup list box. Let’s now tackle the edit control. The TAutoCompleteEdit class Listing B contains the declaration of the TAutoCompleteEdit class. This is a TEdit descendant class that contains two additional properties, Strings and PopupListBox, which are maintained, respectively, by the private members Strings_ and PopupListBox_. These members are initialized in the class constructor, like so:
    __fastcall TAutoCompleteEdit::      TAutoCompleteEdit(TComponent* Owner)       : TEdit(Owner),         Strings_(new TStringList())    {      Strings_->Sorted = true;      Strings_->CaseSensitive = false;      Strings_->Duplicates = dupIgnore;           if (!Designing())      {        // create the popup list box        PopupListBox_ =          new TPopupListBox(this);        PopupListBox_->Edit = this;      }           // Note that the popup list box is      // created only at run-time. The      // Designing() method, defined      // in Listing B, returns a Boolean      // value that specifies the current      // paradigm (true = design time,      // false = run time).    }     
The Strings_ member is a pointer to a TStringList object that will hold a fixed list of the potential strings that can be displayed in the auto-complete list. The PopupListBox_ member is a pointer to a TPopupListBox object that will serve as the graphical interface to the autocomplete list. Updating the pop-up list box At any given time, the pop-up list box (PopupListBox_) can display none, some, or all of the strings held in the Strings_ member, depending on what the user has entered into the edit control. Namely, PopupListBox_ should be displayed only when the edit control’s current text matches one or more of the strings in Strings_. And, when this is the case, PopupListBox_ should list only those strings (of Strings_) that match (either fully or partially) the edit control’s current text. For example, suppose the Strings property is set (at design time) to the following list: Apple Applause Balloon Banana Coconut In this case, the pop-up list box should be shown immediately after the user enters the letter “A”, “B”, or “C”. If the user enters the letter “A”, then only “Apple” and “Applause” should be shown; if the user enters the letter “B”, only “Balloon” and “Banana” should be shown; and, if the user enters the letter “C”, only “Coconut” should be shown. Similarly, if the user enters “Bal”, then only “Balloon” should be shown. In short, Strings_ specifies the master list, and PopupListBox_ displays a variable subset of that list. Here’s the definition of the DoUpdateListBox() method, which populates the list box with the correct strings (of Strings_), depending on the edit control’s current text:
    void TAutoCompleteEdit::      DoUpdatePopupList()    {      // no-op during design time      if (Designing()) return;           // clear the current list      PopupListBox_->Items->Clear();      // grab the edit control's text      const AnsiString edit_text(Text);           // for each string in Strings_      const int count = Strings_->Count;      for (int idx = 0; idx < count;   idx)      {        const AnsiString S(          Strings_->Strings[idx]          );        //if the edit control's text matches        //the current string (S) of Strings_        if (S.Pos(edit_text) == 1)        {          // add S to the popup list box          PopupListBox_->Items->Add(S);        }      }           // hide or show the list box depending      // on whether it contains any items      DoPopupList(PopupListBox_->Count > 0);    }      
The key to this method is the AnsiString::Pos() method, which returns the one-based index of the location at which the substring (edit_text) resides within the master string (S). If Pos() returns 1, then the master string (S) starts with the substring (edit_text), indicating that S should be added to the popup list box. (The pop-up list box is displayed or hidden via the DoPopupList() method, which I’ll define shortly.) The list box’s contents need to be updated whenever the user changes the edit control’s text. To do so, call DoUpdateListBox() from within the Change() method, like so:
    void __fastcall      TAutoCompleteEdit::Change()    {      TEdit::Change();      DoUpdatePopupList();    }     
(Recall that the TEdit::Change() method is called whenever the edit control’s text has changed. It’s from within this method that the TEdit::OnChange event is fired.) Displaying the pop-up list box As I just mentioned, the DoPopupList() method serves to display (or hide) the pop-up list box. Here’s how that method is defined:
void TAutoCompleteEdit::      DoPopupList(bool show)    {      if (!show) {        // dismiss the popup list box        PopupListBox_->Hide();      }      else {        RECT R;        // extract the edit control's        // bounding screen coordinates        if (GetWindowRect(WindowHandle, &R))        {          PopupListBox_->Left = R.left;          PopupListBox_->Top = R.bottom;          PopupListBox_->            Width = R.right - R.left;               // display the popup list box          PopupListBox_->Show();        }      }    }     
Notice that I’ve used the GetWindowRect() API function to determine the location at which to display the list box. This step is necessary because PopupListBox_ is a child of the desktop window; this requires that PopupListBox_’s Left and Top coordinates be specified relative to the top, left corner of the screen. The DoPopupList() method will display (or hide) the list box, but when should this method be called? We’ve already called DoPopupList() from within the DoUpdatePopupList() method, the latter of which is called from within the Change() method. Because the Change() method is called whenever the edit control’s text changes, the majority of cases in which the pop-up list box should be shown or hidden are already covered. There are three other cases in which the pop-up list box should be hidden; I’ll discuss these next. Hiding the list box, part I One situation in which the pop-up list box should be dismissed is when the edit control loses keyboard focus. This can be accomplished by handling the WM_KILLFOCUS message:
void __fastcall TAutoCompleteEdit::      WMKillFocus(TMessage& Msg)    {      TEdit::Dispatch(&Msg);      DoPopupList(false);    }      
Notice from Listing B that the WM_KILLFOCUS message is mapped to the WMKillFocus() method. Within this method, call the DoPopupList() method, with its show parameter set to false, to hide the pop-up list box. Hiding the list box, part II The pop-up list box should also be hidden when the user presses the enter key (either to confirm a selection within the list box, or to submit the edit control’s current text). Call DoPopupList() from within the KeyPress() method:
void __fastcall TAutoCompleteEdit::      KeyPress(char& Key)    {      // if the user has pressed the enter      // key, and we're not in design-mode,      // and the popup list box is visible      if (Key == '\r' && !Designing() &&          PopupListBox_->Visible)      {        const int index =          PopupListBox_->ItemIndex;        if (index != -1)        {          // update the current text          Text = PopupListBox_->            Items->Strings[index];          SelectAll();        }        // hide the popup list box        DoPopupList(false);      }      TEdit::KeyPress(Key);      }     
Notice from this code that, during run-time, if the enter key is pressed and the list box is showing, the list box’s ItemIndex property is used to determine which list box item (if any) is selected. If an item is indeed selected, then the edit control’s text is updated to that item. Finally, the DoPopupList() method is called (with a false show parameter) to dismiss the list box. Before I get to the third situation in which the list box should be hidden, there’s another keystroke related detail I need to mention. Namely, because the edit control retains keyboard focus while the pop-up list box is displayed, the up and down arrow keystrokes need to be forwarded to the list box so that the user can select a different item without using the mouse. This is done (using the SendMessage() function) from within the KeyDown() method, like so:
void __fastcall TAutoCompleteEdit::      KeyDown(Word &Key, TShiftState Shift)    {      TEdit::KeyDown(Key, Shift);      if ((Key == VK_UP || Key == VK_DOWN)            && !Designing()) {        SNDMSG(PopupListBox_->Handle,          WM_KEYDOWN, Key, 0);      }    }    
Hiding the list box, part III There’s one final situation in which th2e pop-up list box should be hidden. Specifically, we’ll need to hide the list box when the user clicks the title bar (or other non-client areas) of the form on which the edit control resides. In fact, this detail is neglected in the Object Inspector of the C Builder IDE, which also uses a pop-up type list box instead of an actual combo box. Namely, if you expand a drop-down property such as Color and then move the Object Inspector, the pop-up list box will remain at its old position—see Figure B. This bug seems to occur only when the Object Inspector is combined in the same parent form, for example, with the Project Manager. Unfortunately, neither the edit control nor the list box receives notification that the user has clicked the title bar. The only way to get such a notification from within the TAutoCompleteEdit class is to either subclass the parent form or hook the application’s message handler. Neither of these approaches is particularly efficient, so I recommend just coding this part outside of the TAutoCompleteEdit class. For example, if your form uses three TAutoCompleteEdit objects, you could handle the WM_NCLBUTTONDOWN message (which is sent to the form when the user clicks any of its non-client areas) as follows:
     class TForm1 : public TForm    {    // other stuff...          private:      MESSAGE void __fastcall         WMNCLButtonDown(TMessage& Msg)      {        AutoCompleteEdit1->PopupList(false);        AutoCompleteEdit2->PopupList(false);        AutoCompleteEdit3->PopupList(false);        TForm::Dispatch(&Msg);            }         public:        BEGIN_MESSAGE_MAP      MESSAGE_HANDLER(WM_NCLBUTTONDOWN,        TMessage, WMNCLButtonDown)    END_MESSAGE_MAP(TForm)    };     
Note that the TAutoCompleteEdit::PopupList() method, which is defined in Listing B, is simply a public interface to the DoPopupList() method. Figure B The Object Inspector fails to dismiss its popup list box when docked with the Project Manager. Conclusion The sample code that accompanies this article demonstrates how to use the TAutoCompleteEdit class, which—aside from the title-bar issue—is fairly self-contained. As a person who hates data entry, I can’t stress enough the importance of a convenient user interface. An auto-complete edit control is one step toward this notion that’s fairly easy to implement. Listing A: Declaration of the TPopupListBox class
class PACKAGE TPopupListBox : public TListBox    {    __published:      __property TCustomEdit* Edit =        {read = Edit_, write = Edit_};         public:      __fastcall TPopupListBox(TComponent* Owner);         protected: // inherited      virtual void __fastcall CreateWnd();      virtual void __fastcall CreateParams(        TCreateParams& Prms);      DYNAMIC void __fastcall MouseMove(        TShiftState Shift, int X, int Y);      DYNAMIC void __fastcall MouseUp(        TMouseButton Button, TShiftState Shift,        int X, int Y);         private:2      TCustomEdit* Edit_;    };         Listing B: Declaration of the TAutoCompleteEdit class         class PACKAGE TAutoCompleteEdit : public TEdit    {    __published:      __property TStringList* Strings =        {read = GetStrings, write = SetStrings};         public:      __fastcall TAutoCompleteEdit(TComponent* Owner);         public:      __property TPopupListBox* PopupListBox =        {read = PopupListBox_};      void PopupList(bool show) { DoPopupList(show); }         protected: // introduced      virtual void DoUpdatePopupList();      virtual void DoPopupList(bool show);         protected: // inherited      DYNAMIC void __fastcall Change();      DYNAMIC void __fastcall KeyDown(Word& Key,        TShiftState Shift);      DYNAMIC void __fastcall KeyPress(char& Key);         private:      bool Designing()        {          return ComponentState.Contains(csDesigning);        }      TStringList* __fastcall GetStrings()        {          return Strings_.get();        }      void __fastcall SetStrings(TStringList* SL)        {          Strings_->Assign(SL);        }      MESSAGE void __fastcall WMKillFocus(        TMessage& Msg);         private:      TPopupListBox* PopupListBox_;      std::auto_ptr Strings_;         public:    BEGIN_MESSAGE_MAP      MESSAGE_HANDLER(WM_KILLFOCUS,    TMessage, WMKillFocus)    END_MESSAGE_MAP(TEdit)    };     
Copyright © 2002, Bridges Publishing. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Bridges Publishing is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners. 聯盟----Visita網站http://www.vista.org.tw ---[ 發問前請先找找舊文章 ]---
系統時間:2024-04-29 20:04:31
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!