TreeView如何做出像ListView多選的功能?? |
答題得分者是:axsoft
|
billlee
一般會員 發表:31 回覆:46 積分:15 註冊:2002-06-25 發送簡訊給我 |
|
jessechan
版主 發表:109 回覆:394 積分:254 註冊:2002-04-05 發送簡訊給我 |
|
axsoft
版主 發表:681 回覆:1056 積分:969 註冊:2002-03-13 發送簡訊給我 |
引言: 各位好: 我想請問各位,TreeView如何做出像ListView多選的功能 ,如果現有的元件,無法做出,請問大家有沒有用過可以達到 我功能的元件,謝謝。參考這個看看! 資料來源: http://www.bridgespublishing.com/ Creating a multiselect tree-viewby Damon Chandler Tree-views are great for displaying data in a compact, hierarchical format. Unfortunately, the standard tree-view control?and thus, the VCL TTreeView component?allows only a single node to be selected at any given time. In this article, I?ll show you how to overcome this limitation. I?ll first give a brief overview of the Custom Draw service and it?s applicability to tree-views; and then I?ll explain how to use this technology to easily implement basic multiple-selection functionality. Custom drawn tree-views A few months back, I explained how to use the Custom Draw service with the TTrackBar control. In that article, I mentioned that many common controls provide Custom Draw notification messages?the tree-view control is no exception. As it turns out, in newer version of C Builder, the TTreeView class exposes Custom-Draw-specific events (e.g., OnCustomDrawItem). I won?t cover these events for two reasons: (1) they?re not available in all versions of C Builder and (2) they?re a bit buggy. Instead, I?ll show you how to handle the NM_CUSTOMDRAW notification message directly. Recall that this notification message is sent to the tree-view?s parent window in the form of a WM_NOTIFY message. Next I?ll explain how to handle this latter message. Handling the WM_NOTIFY message Imagine a form that contains only one child control: a TTreeView object. Because the tree-view is a direct child of the form, the tree-view will send all of its notification messages to the form (i.e., to its parent window). So in this ideal setup, to handle the WM_NOTIFY message, all you need to do is augment the form?s Dispatch() method by using the message-mapping macros:class TForm1 : public TForm { __published: TTreeView *TreeView1; TImageList *ImageList1; private: MESSAGE void __fastcall WMNotify( TMessage& Msg); public: __fastcall TForm1(TComponent* Owner); BEGIN_MESSAGE_MAP MESSAGE_HANDLER( WM_NOTIFY, TMessage, WMNotify) END_MESSAGE_MAP(TForm) };Let me remind you of what this code is doing. The WM_NOTIFY message, as its name suggests, is the tree-view?s way of notifying its parent window of specific events. In this case, because TreeView1?s parent window is Form1, I handle the WM_NOTIFY message by tapping into Form1?s window procedure via the Dispatch() method. The WMNotify() method can now be defined as follows: void __fastcall TForm1:: WMNotify(TMessage& Msg) { // grab a pointer to the tree-view's // notification structure const NM_TREEVIEW* pnmtv = reinterpret_castNotice from this code that I look specifically for the NM_CUSTOMDRAW notification code. This step is necessary because I?m interested only in those events that correspond to the tree-view?s drawing cycle. Also, notice that the NMHDR::hwndFrom data member is tested for equality with the tree-view?s handle. This step ensures that only the notifications that are sent from TreeView1 are handled. (This test isn?t really needed in this ideal case because the form doesn?t contain any other child controls.) So far, so good, but, where?s the code to handle the "Custom Draw stuff"? Well, I didn?t provide that code because you?ll rarely handle the WM_NOTIFY message in this way. Remember, this implementation assumes?and will work only if?TreeView1 is a direct child of Form1. If you later decide that your tree-view will look better if it?s placed on, say, a TPanel object, then this code won?t work. Instead, you?d have to handle the WM_NOTIFY message that?s sent to the panel (i.e., to the tree-view?s new parent window). To avoid this hassle the VCL provides the CN_NOTIFY message, which is a reflected version of WM_NOTIFY that?s sent back to the tree-view itself. Because you handle this reflected message within the tree-view?s window procedure, you don?t have to worry about which window the tree-view is placed on. Handling the CN_NOTIFY message I just mentioned that the CN_NOTIFY message is a reflected version of the WM_NOTIFY message. Because of this fact, you handle the CN_NOTIFY message in the exact same way as you?d handle the WM_NOTIFY message. The only difference is where you handle the message?i.e., in which window procedure. In this case it is done within a descendant class, like so: class TMyTreeView : public TTreeView { public: __fastcall TMyTreeView( TComponent* Owner); private: MESSAGE void __fastcall CNNotify( TMessage& Msg); public: BEGIN_MESSAGE_MAP MESSAGE_HANDLER( CN_NOTIFY, TMessage, CNNotify) END_MESSAGE_MAP(TTreeView) };And, here?s the definition of the TMyTreeView::CNNotify() method: void __fastcall TMyTreeView:: CNNotify(TMessage& Msg) { // grab a pointer to the // tree-view's notification structure const NM_TREEVIEW* pnmtv = reinterpret_castThis code will render every other node in a selected state, regardless of whether the node is actually selected. You can begin to see here how multiselect functionality can be added to a standard tree-view control. At this point the code to render multiple nodes in a selected state is done; what is needed now is to add code to store and manage the selected state of each node. Adding multiselect functionality Listing A provides the definition of a TTreeView descendant class, which I?ve called TMultiTreeView. In the sections that follow, I?ll show you how to implement each of its methods. Getting and setting the selected state As I mentioned earlier, the selected state of each node must be stored; the TTreeNode::Data property can be used for this purpose. Accordingly, I will define a couple of methods to get and set the value of the Data property: inline bool __fastcall TMultiTreeView::IsNodeSelected( TTreeNode* Node ) { if (Node) return Node->Data; return false; } inline void __fastcall TMultiTreeView::SelectNode( TTreeNode* Node, bool select, bool redraw ) { if (Node && Node->Data != reinterpret_castThe definition of the IsNodeSelected() method is fairly simple: it just returns the value that?s held in the specified node?s Data property (implicitly casting the result to a Boolean value). The SelectNode() method serves a similar role: instead of reading the Data property, it assigns the value that?s specified by the select parameter to the node?s Data property. Notice that the Data property is changed only if the select parameter indicates a different value than what?s currently indicated in the node?s Data property. Also notice that the redraw parameter governs whether the RedrawNode() method?which I will explain next?is called. Redrawing a node After you change the selected state of a node, you?ll need a way to incite the tree-view to redraw that node. Specifically, you?ll want the tree-view to send its parent (and thus itself) a Custom Draw notification so that the TMultiTreeView::CNNotify() method?which I?ll define shortly?can redraw the node to reflect its new state. The RedrawWindow() API function can be used to redraw a specific rectangular-based area of a window. In this case, to redraw a specific node, you simply pass the RedrawWindow() function the target node?s bounding rectangle, which you can retrieve by using the TTreeNode::DisplayRect() method. Here?s the code: inline void __fastcall TMultiTreeView::RedrawNode( TTreeNode* Node ) { if (Node) { const RECT R = Node->DisplayRect(true); RedrawWindow( Handle, &R, NULL, RDW_INVALIDATE | RDW_UPDATENOW ); } }Note the use of the RDW_UPDATENOW flag in the call to RedrawWindow(). This specification ensures that the CNNotify() method will be called before RedrawWindow() returns. Now I?ll explain how the CNNotify() method is defined. Drawing the selected nodes I?ve already explained the mechanism to draw multiple nodes in a selected state. Here?s the definition of the TMultiTreeView::CNNotify() method: void __fastcall TMultiTreeView:: CNNotify(TMessage& Msg) { // grab a pointer to the tree-view's // notification structure const NM_TREEVIEW* pnmtv = reinterpret_castNotice that this method is nearly identical to the previous CNNotify() method, which was defined for the TMyTreeView class. Here, instead of just drawing every other node selected, I use the IsNodeSelected() method to decide which nodes to highlight. Although I?ve now shown you how to toggle the selected state of each node, you?ll still need to know when to call the SelectNode() method. Because there are two ways in which a user can select nodes?via the mouse and via the keyboard?you need to provide code to handle both of these situations. Handling multiple selection via the mouse Notice from Listing A that I?ve mapped the WM_LBUTTONDOWN message to the WMLButtonDown() method. This method will be called immediately after the user presses the left mouse button while the cursor is within the tree-view. You can use this opportunity to strategically call the SelectNode() method. Here?s the code: void __fastcall TMultiTreeView:: WMLButtonDown(TMessage& Msg) { // find the node that's hit const POINT PHit = {Msg.LParamLo, Msg.LParamHi}; TTreeNode* HitNode = GetNodeAt(PHit.x, PHit.y); // if a node is hit if (HitNode) { // perform a hit test const TV_HITTESTINFO hit_info = {PHit.x, PHit.y}; TreeView_HitTest(Handle, &hit_info); // if the node's label or icon is hit if (hit_info.flags & TVHT_ONITEM) { // extract the key-state flags const WPARAM keys = Msg.WParam; // if the control key is pressed if (keys & MK_CONTROL) { // if the node is selected if (HitNode->Selected || IsNodeSelected(HitNode)) { // deselect the node HitNode->Selected = false; SelectNode(HitNode, false); return; } // otherwise, select the // currently-selected node SelectNode(Selected, true); } // if the shift key is pressed else if (keys & MK_SHIFT) { // deselect all but from // HitNode to PreviousNode_ SelectAllExcept( HitNode, PreviousNode_, false); // select all nodes from // HitNode to PreviousNode_ SelectNodes( HitNode, PreviousNode_, true); } // otherwise, deselect all nodes else SelectAll(false); } // otherwise, if the user clicks // to the right of the nodes... else if ( hit_info.flags & TVHT_ONITEMRIGHT) { // if the tree-view has focus if (Handle == GetFocus()) { // deselect all nodes SelectAll(false); Selected = NULL; } } } // pass the message on TTreeView::Dispatch(&Msg); }The logic behind this code is as follows: First use the TTreeView::GetNodeAt() method to identify the node that?s closest to the location that was clicked. Next, perform a hit test?by using the TreeView_HitTest() macro?to make sure that the node?s label or icon was hit. If this condition is satisfied, use the WParam value (that accompanies the WM_LBUTTONDOWN message) to determine if the control and/or shift keys were held down during the click. If the control key is held down, simply toggle HitNode?s selected status. Note that when you deselect HitNode, you?ll have to return immediately so that the tree-view doesn?t make the node editable. If the shift key is held down, select a range of nodes. Specifically, select the range from HitNode to the last node that was selected without using the shift key?i.e., to PreviousNode_. (I?ll show you how to set the PreviousNode_ member shortly.) If neither the control key nor the shift key is held down, deselect all nodes and then let the tree-view highlight its real selected node as usual. Finally, notice that?in addition to the SelectNode() method?the WMLButtonDown() method makes use of the SelectNodes(), SelectAllExcept(), and SelectAll() methods. I added these latter three methods to help keep things sane; they?re defined in Listing B. Handling multiple selection via the keyboard To handle the case in which the user selects nodes via the keyboard, you use the WM_KEYDOWN message. From Listing A, you can see that this message is mapped to the WMKeyDown() method, which is defined as follows: void __fastcall TMultiTreeView:: WMKeyDown(TMessage& Msg) { switch (Msg.WParam) { // if the up-arrow key is pressed case VK_UP: { // if the shift key is down if (GetKeyState(VK_SHIFT) < 0) { // get the previous visible node TTreeNode* PrevNode = Selected->GetPrevVisible(); // toggle the currently selected // node based on the selected // state of the previous node SelectNode( Selected, !IsNodeSelected(PrevNode) ); } // otherwise, deselect all nodes else SelectAll(false); break; } // if the down-arrow key is pressed case VK_DOWN: { // if the shift key is down if (GetKeyState(VK_SHIFT) < 0) { // get the next visible node TTreeNode* NextNode = Selected->GetNextVisible(); // toggle the currently selected // node based on the selected // state of the next node SelectNode( Selected, !IsNodeSelected(NextNode) ); } // otherwise, deselect all nodes else SelectAll(false); break; } } // pass the messages on TTreeView::Dispatch(&Msg); }The logic behind this code is as follows: If the user holds down the shift key while navigating down (i.e., pressing the down arrow), grab a pointer to the next visible node, and then toggle the status of the currently-selected node based on the status of this next node. Why do you need to toggle the status of the currently selected node? Well, when the user holds down the shift key, this suggests that he or she intends to select a continuous range of nodes. If the next node isn?t selected, this means that the user has pressed the down arrow to add the next node to his or her current range. The tree-view will, by default, select the next node. But because the tree-view can have only one truly selected node, before it selects the next node, it will deselect the currently selected node. Therefore, when the user wants to add the next node to his or her range, you need to select the currently selected node (i.e., set the node?s Data property) because the tree-view will deselect it. On the other hand, if the user holds down the shift key while navigating down, but the next node isn?t selected, this means that the user wants to remove the currently selected from his or her range. In this case, you need to deselect the currently selected node. A similar argument can be made for the case in which the user has pressed the up arrow. Finally, notice that there?s no code to handle the case in which the user holds down the control key while pressing the arrows. For tree-views, the control key is used for scrolling via the keyboard. Keeping things in sync There?s one final step that needs to be addressed. You?ll want the Data property of tree-view?s truly selected node to always indicate true. This way, you can retrieve the selected nodes by using only the IsNodeSelected() method (within a while loop). Otherwise, you?d also have to test the tree-view?s Selected property. To keep the truly-selected node?s Data property updated, you can use the TTreeView::Change() method like so: void __fastcall TMultiTreeView:: Change(TTreeNode* Node) { // store the previous node that // was selected without using // the shift key if (GetKeyState(VK_SHIFT) >= 0) { PreviousNode_ = Node; } // select the to-be-truly-selected node SelectNode(Node, true, false); TTreeView::Change(Node); }Note that in addition to setting the Data property of the truly selected node, this is where the PreviousNode_ member is set (when the shift key isn?t pressed). Conclusion I?ve shown how to add basic multiple-selection support to a standard tree-view control. You can download the source code for the TMultiTreeView class from www.bridgespublishing.com. I must warn you, however, that this code isn?t well suited for tree-views that contain numerous items (say, greater than 1000). The reason is that the SelectNodes() and SelectAllExcept() methods are relatively expensive because they use the AbsoluteIndex property. There are ways to speed things up (e.g., by using a TList object and/or comparing each node?s bounding rectangle instead of using its AbsoluteIndex property), but I?ll save that for another day. If you do have a large number of items, consider using a virtual-mode TListView object instead. Listing A: Definition of the TMultiTreeView class class TMultiTreeView : public TTreeView { public: __fastcall TMultiTreeView(TComponent* Owner); virtual bool __fastcall IsNodeSelected( TTreeNode* Node); virtual void __fastcall RedrawNode( TTreeNode* Node); virtual void __fastcall SelectNode( TTreeNode* Node, bool select, bool redraw = true); virtual void __fastcall SelectNodes( TTreeNode* NodeA, TTreeNode* NodeB, bool select); virtual void __fastcall SelectAll( bool select); virtual void __fastcall SelectAllExcept( TTreeNode* NodeA, TTreeNode* NodeB, bool select); protected: DYNAMIC void __fastcall Change( TTreeNode* Node); private: TTreeNode* PreviousNode_; MESSAGE void __fastcall CNNotify( TMessage& Msg); MESSAGE void __fastcall WMKeyDown( TMessage& Msg); MESSAGE void __fastcall WMLButtonDown( TMessage& Msg); public: BEGIN_MESSAGE_MAP MESSAGE_HANDLER( CN_NOTIFY, TMessage, CNNotify) MESSAGE_HANDLER( WM_KEYDOWN, TMessage, WMKeyDown) MESSAGE_HANDLER( WM_LBUTTONDOWN, TMessage, WMLButtonDown) END_MESSAGE_MAP(TTreeView) };Listing B: Definition of the TMultiTreeView::SelectNodes(), SelectAll(), and SelectAllExcept() methods //----------------------------------- // Selects all nodes that lie in // the range from NodeA to NodeB // (or from NodeB to NodeA). //----------------------------------- void __fastcall TMultiTreeView::SelectNodes( TTreeNode* NodeA, TTreeNode* NodeB, bool select ) { TTreeNode* FirstNode = NULL; TTreeNode* LastNode = NULL; if (NodeA && NodeB) { if (NodeA->AbsoluteIndex < NodeB->AbsoluteIndex) { FirstNode = NodeA; LastNode = NodeB; } else { FirstNode = NodeB; LastNode = NodeA; } } else if (NodeA && !NodeB) { FirstNode = NodeA; } SelectNode(FirstNode, select); while (FirstNode != LastNode) { FirstNode = FirstNode->GetNext(); SelectNode(FirstNode, select); } } //----------------------------------- // Selects all nodes. //----------------------------------- void __fastcall TMultiTreeView::SelectAll(bool select) { SelectNodes( Items->GetFirstNode(), NULL, select); } //----------------------------------- // Selects all nodes expect those in // the range from NodeA to NodeB. //----------------------------------- void __fastcall TMultiTreeView::SelectAllExcept( TTreeNode* NodeA, TTreeNode* NodeB, bool select ) { if (!NodeA || !NodeB) return; if (NodeA->AbsoluteIndex < NodeB->AbsoluteIndex) { if (NodeA->AbsoluteIndex > 0) { SelectNodes( Items->GetFirstNode(), NodeA->GetPrev(), select); } if (NodeB->AbsoluteIndex < Items->Count - 1) { SelectNodes( NodeB->GetNext(), NULL, select); } } else { if (NodeB->AbsoluteIndex > 0) { SelectNodes( Items->GetFirstNode(), NodeB->GetPrev(), select); } if (NodeA->AbsoluteIndex < Items->Count - 1) { SelectNodes( NodeA->GetNext(), NULL, select); } } }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 ---[ 發問前請先找找舊文章 ]--- 發表人 - axsoft 於 2002/12/10 09:11:49 |
billlee
一般會員 發表:31 回覆:46 積分:15 註冊:2002-06-25 發送簡訊給我 |
|
billlee
一般會員 發表:31 回覆:46 積分:15 註冊:2002-06-25 發送簡訊給我 |
|
axsoft
版主 發表:681 回覆:1056 積分:969 註冊:2002-03-13 發送簡訊給我 |
引言: 再請問一下 我有將axsoft給我的資料去看過,我也去compile過了 但是這是一個class,我不曉得要怎麼去使這個class生效, 是否有人有用過這個東西,可以教我一下,謝謝這有個Source Code 參考一下,與上面的文章內容是同一個 http://www.bridgespublishing.com/articles/source/August02Code.zip 網路志工聯盟----Visita網站http://www.vista.org.tw ---[ 發問前請先找找舊文章 ]--- 發表人 - axsoft 於 2002/12/11 15:05:11 |
billlee
一般會員 發表:31 回覆:46 積分:15 註冊:2002-06-25 發送簡訊給我 |
引言:axsoft你好 這個的程式碼我有下載過來看過了,只有看到你給我的資料一開始定義在Form上執行的方法,我想要知道的是後面不是有定義class嗎,我想知道要怎麼用TMultiTreeView這個Class,謝謝引言: 再請問一下 我有將axsoft給我的資料去看過,我也去compile過了 但是這是一個class,我不曉得要怎麼去使這個class生效, 是否有人有用過這個東西,可以教我一下,謝謝這有個Source Code 參考一下,與上面的文章內容是同一個 http://www.bridgespublishing.com/articles/source/August02Code.zip 網路志工聯盟----Visita網站http://www.vista.org.tw ---[ 發問前請先找找舊文章 ]--- 發表人 - axsoft 於 2002/12/11 15:05:11 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |