Skip to main content

native_windows_gui2/controls/
treeview.rs

1/*!
2A tree-view control is a window that displays a hierarchical list of items
3*/
4
5use super::{ControlBase, ControlHandle};
6use crate::win32::base_helper::{check_hwnd, from_utf16, to_utf16};
7use crate::win32::window_helper as wh;
8use crate::{Font, NwgError};
9use std::{mem, ptr};
10use winapi::shared::minwindef::{LPARAM, WPARAM};
11use winapi::um::commctrl::{HTREEITEM, TVIS_EXPANDED, TVIS_SELECTED, TVITEMW, TVS_SHOWSELALWAYS};
12use winapi::um::winuser::{WS_DISABLED, WS_TABSTOP, WS_VISIBLE};
13
14#[cfg(feature = "image-list")]
15use winapi::um::commctrl::HIMAGELIST;
16
17#[cfg(feature = "image-list")]
18use crate::ImageList;
19
20const NOT_BOUND: &'static str = "TreeView is not yet bound to a winapi object";
21const BAD_HANDLE: &'static str = "INTERNAL ERROR: TreeView handle is not HWND!";
22
23bitflags! {
24    /**
25        The tree view flags
26
27        * VISIBLE:  The tree view is immediatly visible after creation
28        * DISABLED: The tree view cannot be interacted with by the user. It also has a grayed out look.
29        * TAB_STOP: The tree view can be selected using tab navigation
30    */
31    pub struct TreeViewFlags: u32 {
32        const VISIBLE = WS_VISIBLE;
33        const DISABLED = WS_DISABLED;
34        const TAB_STOP = WS_TABSTOP;
35        const ALWAYS_SHOW_SELECTION = TVS_SHOWSELALWAYS;
36    }
37}
38
39bitflags! {
40    /**
41        A tree item state
42
43        * SELECTED:  The tree view is immediatly visible after creation
44        * DISABLED: The tree view cannot be interacted with by the user. It also has a grayed out look.
45        * TAB_STOP: The tree view can be selected using tab navigation
46    */
47    pub struct TreeItemState: u32 {
48        const SELECTED = TVIS_SELECTED;
49        const EXPANDED = TVIS_EXPANDED;
50    }
51}
52
53/// Select the position of a new item that is about to be inserted in a TreeView
54#[derive(Copy, Clone, Debug)]
55pub enum TreeInsert {
56    /// Inserts the item at the beginning of the list.
57    First,
58
59    /// Inserts the item at the end of the list.
60    Last,
61
62    /// Add the item as a root item
63    Root,
64
65    /// Inserts the item into the list in alphabetical order
66    Sort,
67
68    /// Insert the item after the choosen item
69    After(HTREEITEM),
70}
71
72/// Possible state of a tree item regarding the "expanded/collapsed" state
73#[derive(Copy, Clone, Debug)]
74#[repr(u8)]
75pub enum ExpandState {
76    Collapse,
77    CollapseReset,
78    Expand,
79    ExpandPartial,
80    Toggle,
81}
82
83/// An action that can be applied to a tree item. Used in events
84#[derive(Copy, Clone, Debug)]
85pub enum TreeItemAction {
86    /// An unexpected value was passed to NWG
87    Unknown,
88
89    /// A tree item was expanded or collapsed.
90    Expand(ExpandState),
91
92    /// The state of the item was changed
93    State {
94        old: TreeItemState,
95        new: TreeItemState,
96    },
97}
98
99/// A reference to an item in a TreeView
100#[derive(Debug)]
101pub struct TreeItem {
102    pub handle: HTREEITEM,
103}
104
105impl TreeItem {
106    /// Checks if the inner handle is null
107    pub fn is_null(&self) -> bool {
108        self.handle.is_null()
109    }
110}
111
112/**
113A tree-view control is a window that displays a hierarchical list of items.
114
115While a treeview can support selected multiple item programatically (using `select_item`), this is not fully supported
116by the winapi implementation.
117
118Requires the `tree-view` feature
119
120**Builder parameters:**
121  * `parent`:     **Required.** The tree-view parent container.
122  * `position`:   The treeview position.
123  * `enabled`:    If the treeview can be used by the user. It also has a grayed out look if disabled.
124  * `focus`:      The control receive focus after being created
125  * `flags`:      A combination of the `TreeViewFlags` values.
126  * `ex_flags`:   A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
127  * `font`:       The font used for the treeview text
128  * `parent`:     The treeview parent container.
129  * `image_list`: Image list containing the icon to use in the tree-view
130
131**Control events:**
132  * `MousePress(_)`: Generic mouse press events on the tree view
133  * `OnMouseMove`: Generic mouse mouse event
134  * `OnMouseWheel`: Generic mouse wheel event
135  * `OnTreeViewClick`: When the user has clicked the left mouse button within the control.
136  * `OnTreeViewDoubleClick`: When the user has clicked the left mouse button within the control twice rapidly.
137  * `OnTreeViewRightClick`: When the user has clicked the right mouse button within the control.
138  * `OnTreeFocusLost`: When the control has lost the input focus
139  * `OnTreeFocus`: When the control has acquired the input focus
140  * `OnTreeItemDelete`: Just before an item is deleted. Also sent for all the children.
141  * `OnTreeItemExpanded`: After an item was expanded or collapsed. Sends a `EventData::OnTreeItemUpdate`.
142  * `OnTreeItemChanged`: After the state of an item was changed. Sends a `EventData::OnTreeItemUpdate`.
143  * `OnTreeItemSelectionChanged`: After the current selection was changed. Sends a `EventData::OnTreeItemChanged`.
144*/
145#[derive(Default, PartialEq, Eq)]
146pub struct TreeView {
147    pub handle: ControlHandle,
148}
149
150impl TreeView {
151    pub fn builder<'a>() -> TreeViewBuilder<'a> {
152        TreeViewBuilder {
153            size: (100, 200),
154            position: (0, 0),
155            enabled: true,
156            focus: false,
157            flags: None,
158            ex_flags: 0,
159            font: None,
160            parent: None,
161
162            #[cfg(feature = "image-list")]
163            image_list: None,
164        }
165    }
166
167    /// Sets the image list of the treeview
168    #[cfg(feature = "image-list")]
169    pub fn set_image_list(&self, list: Option<&ImageList>) {
170        use winapi::um::commctrl::{TVM_SETIMAGELIST, TVSIL_NORMAL};
171
172        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
173        let list_handle = list.map(|l| l.handle).unwrap_or(ptr::null_mut());
174
175        wh::send_message(handle, TVM_SETIMAGELIST, TVSIL_NORMAL, list_handle as _);
176    }
177
178    /// Returns the image list of the treeview or None if there is none.
179    /// The returned image list is not owned
180    #[cfg(feature = "image-list")]
181    pub fn image_list(&self) -> Option<ImageList> {
182        use winapi::um::commctrl::{TVM_GETIMAGELIST, TVSIL_NORMAL};
183
184        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
185        let handle = wh::send_message(handle, TVM_GETIMAGELIST, TVSIL_NORMAL, 0) as HIMAGELIST;
186        if handle.is_null() {
187            None
188        } else {
189            Some(ImageList {
190                handle,
191                owned: false,
192            })
193        }
194    }
195
196    /// Sets the image that will appear left to the item text. `index` is the index of the image in the image-list
197    /// Won't do anything if the control do not have a image list or if the item is not in the tree
198    /// If `on_select` is set to true, sets the icon that is used when an item is active
199    #[cfg(feature = "image-list")]
200    pub fn set_item_image(&self, item: &TreeItem, index: i32, on_select: bool) {
201        use winapi::um::commctrl::{TVIF_IMAGE, TVIF_SELECTEDIMAGE, TVM_SETITEMW};
202
203        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
204        let mut tree_item = blank_item();
205        tree_item.hItem = item.handle;
206
207        tree_item.mask = match on_select {
208            true => TVIF_SELECTEDIMAGE,
209            false => TVIF_IMAGE,
210        };
211
212        match on_select {
213            true => {
214                tree_item.iSelectedImage = index;
215            }
216            false => {
217                tree_item.iImage = index;
218            }
219        }
220
221        wh::send_message(
222            handle,
223            TVM_SETITEMW,
224            0,
225            &mut tree_item as *mut TVITEMW as LPARAM,
226        );
227    }
228
229    /// Returns the index of the image in the tree view image list.
230    /// If there is no image list in the control or the item is not in the control, 0 will be returned.
231    /// If `on_select` is set to true, returns the icon that is used when an item is active
232    #[cfg(feature = "image-list")]
233    pub fn item_image(&self, item: &TreeItem, on_select: bool) -> i32 {
234        use winapi::um::commctrl::{TVIF_IMAGE, TVIF_SELECTEDIMAGE, TVM_GETITEMW};
235
236        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
237        let mut tree_item = blank_item();
238        tree_item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE;
239        tree_item.hItem = item.handle;
240
241        match wh::send_message(
242            handle,
243            TVM_GETITEMW,
244            0,
245            &mut tree_item as *mut TVITEMW as LPARAM,
246        ) {
247            0 => 0,
248            _ => match on_select {
249                true => tree_item.iSelectedImage,
250                false => tree_item.iImage,
251            },
252        }
253    }
254
255    /// Sets the text color in the treeview
256    pub fn set_text_color(&self, r: u8, g: u8, b: u8) {
257        use winapi::um::commctrl::TVM_SETTEXTCOLOR;
258        use winapi::um::wingdi::RGB;
259
260        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
261        let color = RGB(r, g, b);
262
263        wh::send_message(handle, TVM_SETTEXTCOLOR, 0, color as _);
264
265        self.invalidate();
266    }
267
268    /// Returns the text color in the treeview
269    pub fn text_color(&self) -> [u8; 3] {
270        use winapi::um::commctrl::TVM_GETTEXTCOLOR;
271        use winapi::um::wingdi::{GetBValue, GetGValue, GetRValue};
272
273        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
274
275        let col = wh::send_message(handle, TVM_GETTEXTCOLOR, 0, 0) as u32;
276
277        [GetRValue(col), GetGValue(col), GetBValue(col)]
278    }
279
280    /// Retrieves the amount, in pixels, that child items are indented relative to their parent items.
281    pub fn indent(&self) -> u32 {
282        use winapi::um::commctrl::TVM_GETINDENT;
283
284        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
285        wh::send_message(handle, TVM_GETINDENT, 0, 0) as u32
286    }
287
288    /// Sets the width of indentation for a tree-view control and redraws the control to reflect the new width.
289    pub fn set_indent(&self, indent: u32) {
290        use winapi::um::commctrl::TVM_SETINDENT;
291
292        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
293        wh::send_message(handle, TVM_SETINDENT, indent as _, 0);
294    }
295
296    /// Return the root item of the tree view if one is present.
297    /// If there is no root in the tree, returns `None`.
298    pub fn root(&self) -> Option<TreeItem> {
299        use winapi::um::commctrl::TVGN_ROOT;
300        next_treeview_item(&self.handle, TVGN_ROOT, ptr::null_mut())
301    }
302
303    /// Returns the first child of an item or `None` if the item has no child or if it's not part of the tree view
304    /// To iterate over all the children, use `TreeView.iter_item(&parent_item)`
305    pub fn first_child(&self, item: &TreeItem) -> Option<TreeItem> {
306        use winapi::um::commctrl::TVGN_CHILD;
307        next_treeview_item(&self.handle, TVGN_CHILD, item.handle)
308    }
309
310    /// Returns the next sibling in the tree or `None` if the item has no more sibling or if it's not part of the tree view
311    pub fn next_sibling(&self, item: &TreeItem) -> Option<TreeItem> {
312        use winapi::um::commctrl::TVGN_NEXT;
313        next_treeview_item(&self.handle, TVGN_NEXT, item.handle)
314    }
315
316    /// Returns the previous sibling in the tree or `None` if the item has no more sibling or if it's not part of the tree view
317    pub fn previous_sibling(&self, item: &TreeItem) -> Option<TreeItem> {
318        use winapi::um::commctrl::TVGN_PREVIOUS;
319        next_treeview_item(&self.handle, TVGN_PREVIOUS, item.handle)
320    }
321
322    /// Returns the parent of the item in the tree or `None` if the item is root
323    pub fn parent(&self, item: &TreeItem) -> Option<TreeItem> {
324        use winapi::um::commctrl::TVGN_PARENT;
325        next_treeview_item(&self.handle, TVGN_PARENT, item.handle)
326    }
327
328    /// Return the currently selected item. If there are more than one selected item, returns the first one.
329    /// If there is no selected item, returns `None`.
330    pub fn selected_item(&self) -> Option<TreeItem> {
331        use winapi::um::commctrl::{TVGN_NEXTSELECTED, TVM_GETNEXTITEM};
332
333        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
334        let tree_handle =
335            wh::send_message(handle, TVM_GETNEXTITEM, TVGN_NEXTSELECTED, 0) as HTREEITEM;
336        if tree_handle.is_null() {
337            None
338        } else {
339            Some(TreeItem {
340                handle: tree_handle,
341            })
342        }
343    }
344
345    /// Returns the selected items in a Treeview
346    /// If there is no selected items, returns an empty `Vec`.
347    pub fn selected_items(&self) -> Vec<TreeItem> {
348        use winapi::um::commctrl::{TVGN_NEXTSELECTED, TVM_GETNEXTITEM};
349
350        let mut items = Vec::new();
351
352        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
353        let mut last_handle = wh::send_message(handle, TVM_GETNEXTITEM, TVGN_NEXTSELECTED, 0);
354        while last_handle != 0 {
355            items.push(TreeItem {
356                handle: last_handle as _,
357            });
358            last_handle =
359                wh::send_message(handle, TVM_GETNEXTITEM, TVGN_NEXTSELECTED, last_handle as _);
360        }
361
362        items
363    }
364
365    /// Returns the number of selected item in the tree view
366    pub fn selected_item_count(&self) -> usize {
367        use winapi::um::commctrl::{TVGN_NEXTSELECTED, TVM_GETNEXTITEM};
368
369        let mut count = 0;
370        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
371
372        let mut last_handle = wh::send_message(handle, TVM_GETNEXTITEM, TVGN_NEXTSELECTED, 0);
373        while last_handle != 0 {
374            count += 1;
375            last_handle =
376                wh::send_message(handle, TVM_GETNEXTITEM, TVGN_NEXTSELECTED, last_handle as _);
377        }
378
379        count
380    }
381
382    /// Insert a new item into the TreeView and return a reference to new newly added item
383    pub fn insert_item<'a>(
384        &self,
385        new: &'a str,
386        parent: Option<&TreeItem>,
387        position: TreeInsert,
388    ) -> TreeItem {
389        use winapi::um::commctrl::TVINSERTSTRUCTW_u;
390        use winapi::um::commctrl::{
391            TVI_FIRST, TVI_LAST, TVI_ROOT, TVI_SORT, TVIF_TEXT, TVINSERTSTRUCTW, TVM_INSERTITEMW,
392        };
393        use winapi::um::winnt::LPWSTR;
394
395        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
396
397        let insert = match position {
398            TreeInsert::First => TVI_FIRST,
399            TreeInsert::Last => TVI_LAST,
400            TreeInsert::Root => TVI_ROOT,
401            TreeInsert::Sort => TVI_SORT,
402            TreeInsert::After(i) => i,
403        };
404
405        let text = to_utf16(new);
406
407        let item = {
408            let mut item: TVINSERTSTRUCTW_u = unsafe { mem::zeroed() };
409            let i = unsafe { item.item_mut() };
410            i.mask = TVIF_TEXT;
411            i.pszText = text.as_ptr() as LPWSTR;
412            item
413        };
414
415        let new_item = TVINSERTSTRUCTW {
416            hParent: parent.map(|p| p.handle).unwrap_or(ptr::null_mut()),
417            hInsertAfter: insert,
418            u: item,
419        };
420
421        let ptr = &new_item as *const TVINSERTSTRUCTW;
422        let handle = wh::send_message(handle, TVM_INSERTITEMW, 0, ptr as LPARAM) as HTREEITEM;
423
424        self.invalidate();
425
426        TreeItem { handle }
427    }
428
429    /// Insert a new item into the TreeView with associated lParam and return a reference to new newly added item
430    pub fn insert_item_with_param<'a>(
431        &self,
432        new: &'a str,
433        parent: Option<&TreeItem>,
434        position: TreeInsert,
435        data: isize,
436    ) -> TreeItem {
437        use winapi::um::commctrl::TVINSERTSTRUCTW_u;
438        use winapi::um::commctrl::{
439            TVI_FIRST, TVI_LAST, TVI_ROOT, TVI_SORT, TVIF_PARAM, TVIF_TEXT, TVINSERTSTRUCTW,
440            TVM_INSERTITEMW,
441        };
442        use winapi::um::winnt::LPWSTR;
443
444        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
445
446        let insert = match position {
447            TreeInsert::First => TVI_FIRST,
448            TreeInsert::Last => TVI_LAST,
449            TreeInsert::Root => TVI_ROOT,
450            TreeInsert::Sort => TVI_SORT,
451            TreeInsert::After(i) => i,
452        };
453
454        let text = to_utf16(new);
455
456        let item = {
457            let mut item: TVINSERTSTRUCTW_u = unsafe { mem::zeroed() };
458            let i = unsafe { item.item_mut() };
459            i.mask = TVIF_TEXT | TVIF_PARAM;
460            i.pszText = text.as_ptr() as LPWSTR;
461            i.lParam = data;
462            item
463        };
464
465        let new_item = TVINSERTSTRUCTW {
466            hParent: parent.map(|p| p.handle).unwrap_or(ptr::null_mut()),
467            hInsertAfter: insert,
468            u: item,
469        };
470
471        let ptr = &new_item as *const TVINSERTSTRUCTW;
472        let handle = wh::send_message(handle, TVM_INSERTITEMW, 0, ptr as LPARAM) as HTREEITEM;
473
474        self.invalidate();
475
476        TreeItem { handle }
477    }
478
479    /// Remove an item and its children from the tree view
480    pub fn remove_item(&self, item: &TreeItem) {
481        use winapi::um::commctrl::TVM_DELETEITEM;
482
483        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
484        wh::send_message(handle, TVM_DELETEITEM, 0, item.handle as LPARAM);
485    }
486
487    /// Selects the specified tree-view item and scrolls the item into view.
488    pub fn select_item(&self, item: &TreeItem) {
489        use winapi::um::commctrl::{TVIF_STATE, TVM_SETITEMW};
490
491        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
492        let mut tree_item = blank_item();
493        tree_item.mask = TVIF_STATE;
494        tree_item.hItem = item.handle;
495        tree_item.state = TVIS_SELECTED;
496        tree_item.stateMask = TVIS_SELECTED;
497
498        wh::send_message(
499            handle,
500            TVM_SETITEMW,
501            0,
502            &mut tree_item as *mut TVITEMW as LPARAM,
503        );
504    }
505
506    /// Unselects an item from the treeview
507    pub fn unselect_item(&self, item: &TreeItem) {
508        use winapi::um::commctrl::{TVIF_STATE, TVM_SETITEMW};
509
510        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
511        let mut tree_item = blank_item();
512        tree_item.mask = TVIF_STATE;
513        tree_item.hItem = item.handle;
514        tree_item.state = 0;
515        tree_item.stateMask = TVIS_SELECTED;
516
517        wh::send_message(
518            handle,
519            TVM_SETITEMW,
520            0,
521            &mut tree_item as *mut TVITEMW as LPARAM,
522        );
523    }
524
525    /// Creates an iterator over the tree view items
526    #[cfg(feature = "tree-view-iterator")]
527    pub fn iter<'a>(&'a self) -> crate::TreeViewIterator<'a> {
528        check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
529        crate::TreeViewIterator::new(self, ptr::null_mut())
530    }
531
532    /// Creates an iterator over the children of an item. This does not include the item itself.
533    #[cfg(feature = "tree-view-iterator")]
534    pub fn iter_item<'a>(&'a self, item: &TreeItem) -> crate::TreeViewIterator<'a> {
535        check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
536        crate::TreeViewIterator::new(self, item.handle)
537    }
538
539    /// Returns the text of the selected item. Return None if the item is not in the tree view.
540    /// The returned text value cannot be bigger than 260 characters
541    pub fn item_text(&self, tree_item: &TreeItem) -> Option<String> {
542        use winapi::um::commctrl::{TVIF_HANDLE, TVIF_TEXT, TVM_GETITEMW};
543        const BUFFER_MAX: usize = 260;
544
545        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
546
547        let mut text_buffer = Vec::with_capacity(BUFFER_MAX);
548        unsafe {
549            text_buffer.set_len(BUFFER_MAX);
550        }
551
552        let mut item: TVITEMW = blank_item();
553        item.mask = TVIF_TEXT | TVIF_HANDLE;
554        item.hItem = tree_item.handle;
555        item.pszText = text_buffer.as_mut_ptr();
556        item.cchTextMax = BUFFER_MAX as _;
557
558        let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
559        if result == 0 {
560            return None;
561        }
562
563        Some(from_utf16(&text_buffer))
564    }
565
566    /// Set the text for specified item in the treeview.
567    pub fn set_item_text(&self, tree_item: &TreeItem, new_text: &str) {
568        use winapi::um::commctrl::{TVIF_TEXT, TVM_SETITEMW};
569        use winapi::um::winnt::LPWSTR;
570
571        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
572        let text = to_utf16(new_text);
573
574        let mut item: TVITEMW = blank_item();
575        item.mask = TVIF_TEXT;
576        item.hItem = tree_item.handle;
577        item.pszText = text.as_ptr() as LPWSTR;
578
579        wh::send_message(handle, TVM_SETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
580    }
581
582    /// Returns the lParam of the selected item. Return None if the item is not in the tree view.
583    pub fn item_param(&self, tree_item: &TreeItem) -> Option<isize> {
584        use winapi::um::commctrl::{TVIF_HANDLE, TVIF_PARAM, TVM_GETITEMW};
585
586        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
587
588        let mut item: TVITEMW = blank_item();
589        item.mask = TVIF_HANDLE | TVIF_PARAM;
590        item.hItem = tree_item.handle;
591
592        let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
593        if result == 0 {
594            return None;
595        }
596
597        Some(item.lParam)
598    }
599
600    /// Returns `true` if the tree view item has children. Returns `None` if the item is not in the tree view.
601    pub fn item_has_children(&self, tree_item: &TreeItem) -> Option<bool> {
602        use winapi::um::commctrl::{TVIF_CHILDREN, TVIF_HANDLE, TVM_GETITEMW};
603
604        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
605
606        let mut item: TVITEMW = blank_item();
607        item.hItem = tree_item.handle;
608        item.mask = TVIF_CHILDREN | TVIF_HANDLE;
609
610        let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
611        if result == 0 {
612            return None;
613        }
614
615        Some(item.cChildren != 0)
616    }
617
618    /// Returns the item state in the tree view or `None` if the item is not in the tree view
619    pub fn item_state(&self, tree_item: &TreeItem) -> Option<TreeItemState> {
620        use winapi::um::commctrl::{TVIF_HANDLE, TVIF_STATE, TVM_GETITEMW};
621
622        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
623
624        let mut item: TVITEMW = unsafe { mem::zeroed() };
625        item.hItem = tree_item.handle;
626        item.mask = TVIF_STATE | TVIF_HANDLE;
627        item.stateMask = 0xFF;
628
629        let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
630        if result == 0 {
631            return None;
632        }
633
634        Some(TreeItemState::from_bits_truncate(item.state))
635    }
636
637    /// Expands or collapses the list of child items associated with the specified parent item, if any.
638    pub fn set_expand_state(&self, item: &TreeItem, state: ExpandState) {
639        use winapi::um::commctrl::{
640            TVE_COLLAPSE, TVE_COLLAPSERESET, TVE_EXPAND, TVE_EXPANDPARTIAL, TVE_TOGGLE, TVM_EXPAND,
641        };
642
643        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
644
645        let state = match state {
646            ExpandState::Collapse => TVE_COLLAPSE,
647            ExpandState::CollapseReset => TVE_COLLAPSE | TVE_COLLAPSERESET,
648            ExpandState::Expand => TVE_EXPAND,
649            ExpandState::ExpandPartial => TVE_EXPANDPARTIAL,
650            ExpandState::Toggle => TVE_TOGGLE,
651        };
652
653        wh::send_message(handle, TVM_EXPAND, state as WPARAM, item.handle as LPARAM);
654    }
655
656    /// Ensures that a tree-view item is visible, expanding the parent item or scrolling the tree-view control, if necessary.
657    pub fn ensure_visible(&self, item: &TreeItem) {
658        use winapi::um::commctrl::TVM_ENSUREVISIBLE;
659
660        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
661        wh::send_message(handle, TVM_ENSUREVISIBLE, 0, item.handle as LPARAM);
662    }
663
664    /// Remove every item from the treeview by removing the root item
665    pub fn clear(&self) {
666        use winapi::um::commctrl::{TVI_ROOT, TVM_DELETEITEM};
667
668        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
669        wh::send_message(handle, TVM_DELETEITEM, 0, TVI_ROOT as LPARAM);
670    }
671
672    /// Return the total number of item in the tree view
673    pub fn len(&self) -> usize {
674        use winapi::um::commctrl::TVM_GETCOUNT;
675
676        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
677        wh::send_message(handle, TVM_GETCOUNT, 0, 0) as usize
678    }
679
680    /// Return the number of item in the tree view visible by the user
681    pub fn visible_len(&self) -> usize {
682        use winapi::um::commctrl::TVM_GETVISIBLECOUNT;
683
684        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
685        wh::send_message(handle, TVM_GETVISIBLECOUNT, 0, 0) as usize
686    }
687
688    //
689    // Common methods
690    //
691
692    /// Invalidate the whole drawing region.
693    pub fn invalidate(&self) {
694        use winapi::um::winuser::InvalidateRect;
695
696        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
697        unsafe {
698            InvalidateRect(handle, ptr::null(), 1);
699        }
700    }
701
702    /// Return the font of the control
703    pub fn font(&self) -> Option<Font> {
704        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
705
706        let font_handle = wh::get_window_font(handle);
707        if font_handle.is_null() {
708            None
709        } else {
710            Some(Font {
711                handle: font_handle,
712            })
713        }
714    }
715
716    /// Set the font of the control
717    pub fn set_font(&self, font: Option<&Font>) {
718        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
719
720        wh::set_window_font(handle, font.map(|f| f.handle), true);
721    }
722
723    /// Return true if the control currently has the keyboard focus
724    pub fn focus(&self) -> bool {
725        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
726        wh::get_focus(handle)
727    }
728
729    /// Set the keyboard focus on the button.
730    pub fn set_focus(&self) {
731        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
732
733        wh::set_focus(handle);
734    }
735
736    /// Return true if the control user can interact with the control, return false otherwise
737    pub fn enabled(&self) -> bool {
738        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
739        wh::get_window_enabled(handle)
740    }
741
742    /// Enable or disable the control
743    pub fn set_enabled(&self, v: bool) {
744        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
745        wh::set_window_enabled(handle, v)
746    }
747
748    /// Return true if the control is visible to the user. Will return true even if the
749    /// control is outside of the parent client view (ex: at the position (10000, 10000))
750    pub fn visible(&self) -> bool {
751        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
752        wh::get_window_visibility(handle)
753    }
754
755    /// Show or hide the control to the user
756    pub fn set_visible(&self, v: bool) {
757        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
758        wh::set_window_visibility(handle, v)
759    }
760
761    /// Return the size of the button in the parent window
762    pub fn size(&self) -> (u32, u32) {
763        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
764        wh::get_window_size(handle)
765    }
766
767    /// Set the size of the button in the parent window
768    pub fn set_size(&self, x: u32, y: u32) {
769        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
770        wh::set_window_size(handle, x, y, false)
771    }
772
773    /// Return the position of the button in the parent window
774    pub fn position(&self) -> (i32, i32) {
775        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
776        wh::get_window_position(handle)
777    }
778
779    /// Set the position of the button in the parent window
780    pub fn set_position(&self, x: i32, y: i32) {
781        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
782        wh::set_window_position(handle, x, y)
783    }
784
785    /// Winapi class name used during control creation
786    pub fn class_name(&self) -> &'static str {
787        winapi::um::commctrl::WC_TREEVIEW
788    }
789
790    /// Winapi base flags used during window creation
791    pub fn flags(&self) -> u32 {
792        use winapi::um::commctrl::{TVS_EDITLABELS, TVS_HASBUTTONS, TVS_HASLINES, TVS_LINESATROOT};
793
794        WS_VISIBLE
795            | TVS_HASBUTTONS
796            | TVS_LINESATROOT
797            | TVS_HASLINES
798            | WS_TABSTOP
799            | TVS_SHOWSELALWAYS
800            | TVS_EDITLABELS
801    }
802
803    /// Winapi flags required by the control
804    pub fn forced_flags(&self) -> u32 {
805        use winapi::um::commctrl::TVS_NOTOOLTIPS;
806        use winapi::um::winuser::{WS_BORDER, WS_CHILD};
807
808        WS_CHILD | WS_BORDER | TVS_NOTOOLTIPS
809    }
810
811    /// Begins to in-place edit the specified item's text.
812    /// Return None if Failed.
813    /// Return the treeview's handle if successful.
814    pub fn edit_label(&self, item: &TreeItem) -> Option<ControlHandle> {
815        use winapi::shared::windef::HWND;
816        use winapi::um::commctrl::TVM_EDITLABELW;
817        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
818
819        let result = wh::send_message(
820            handle,
821            TVM_EDITLABELW,
822            0,
823            item.handle as HTREEITEM as LPARAM,
824        );
825
826        if result == 0 {
827            return None;
828        }
829        Some(ControlHandle::Hwnd(result as HWND))
830    }
831
832    /// End the in-place editing of the tree item's label.
833    /// The parameter f_cancel indicates whether the editing is canceled without being saved to the label.
834    /// If this parameter is TRUE, the system cancels editing without saving the changes. Otherwise, the system saves the changes to the label.
835    /// Return true if successful, otherwise return false.
836    pub fn end_edit_label_now(&self, f_cancel: bool) -> bool {
837        use winapi::um::commctrl::TVM_ENDEDITLABELNOW;
838        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
839
840        wh::send_message(handle, TVM_ENDEDITLABELNOW, f_cancel as WPARAM, 0) != 0
841    }
842}
843
844impl Drop for TreeView {
845    fn drop(&mut self) {
846        self.handle.destroy();
847    }
848}
849
850/// Builder for a TreeView
851pub struct TreeViewBuilder<'a> {
852    size: (i32, i32),
853    position: (i32, i32),
854    enabled: bool,
855    focus: bool,
856    flags: Option<TreeViewFlags>,
857    ex_flags: u32,
858    font: Option<&'a Font>,
859    parent: Option<ControlHandle>,
860
861    #[cfg(feature = "image-list")]
862    image_list: Option<&'a ImageList>,
863}
864
865impl<'a> TreeViewBuilder<'a> {
866    pub fn flags(mut self, flags: TreeViewFlags) -> TreeViewBuilder<'a> {
867        self.flags = Some(flags);
868        self
869    }
870
871    pub fn ex_flags(mut self, flags: u32) -> TreeViewBuilder<'a> {
872        self.ex_flags = flags;
873        self
874    }
875
876    pub fn size(mut self, size: (i32, i32)) -> TreeViewBuilder<'a> {
877        self.size = size;
878        self
879    }
880
881    pub fn position(mut self, pos: (i32, i32)) -> TreeViewBuilder<'a> {
882        self.position = pos;
883        self
884    }
885
886    pub fn enabled(mut self, e: bool) -> TreeViewBuilder<'a> {
887        self.enabled = e;
888        self
889    }
890
891    pub fn focus(mut self, focus: bool) -> TreeViewBuilder<'a> {
892        self.focus = focus;
893        self
894    }
895
896    pub fn font(mut self, font: Option<&'a Font>) -> TreeViewBuilder<'a> {
897        self.font = font;
898        self
899    }
900
901    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TreeViewBuilder<'a> {
902        self.parent = Some(p.into());
903        self
904    }
905
906    #[cfg(feature = "image-list")]
907    pub fn image_list(mut self, list: Option<&'a ImageList>) -> TreeViewBuilder<'a> {
908        self.image_list = list;
909        self
910    }
911
912    pub fn build(self, out: &mut TreeView) -> Result<(), NwgError> {
913        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
914
915        let parent = match self.parent {
916            Some(p) => Ok(p),
917            None => Err(NwgError::no_parent("TreeView")),
918        }?;
919
920        *out = Default::default();
921
922        out.handle = ControlBase::build_hwnd()
923            .class_name(out.class_name())
924            .forced_flags(out.forced_flags())
925            .flags(flags)
926            .ex_flags(self.ex_flags)
927            .size(self.size)
928            .position(self.position)
929            .parent(Some(parent))
930            .build()?;
931
932        if self.font.is_some() {
933            out.set_font(self.font);
934        } else {
935            out.set_font(Font::global_default().as_ref());
936        }
937
938        builder_set_image_list(&self, out);
939
940        if self.focus {
941            out.set_focus();
942        }
943
944        out.set_enabled(self.enabled);
945
946        Ok(())
947    }
948}
949
950impl PartialEq for TreeItem {
951    fn eq(&self, other: &Self) -> bool {
952        self.handle == other.handle
953    }
954}
955
956impl Eq for TreeItem {}
957
958fn next_treeview_item(handle: &ControlHandle, action: usize, item: HTREEITEM) -> Option<TreeItem> {
959    use winapi::um::commctrl::TVM_GETNEXTITEM;
960
961    if handle.blank() {
962        panic!("{}", NOT_BOUND);
963    }
964    let handle = handle.hwnd().expect(BAD_HANDLE);
965
966    let handle = wh::send_message(handle, TVM_GETNEXTITEM, action as _, item as _) as HTREEITEM;
967    if handle.is_null() {
968        None
969    } else {
970        Some(TreeItem { handle })
971    }
972}
973
974#[cfg(feature = "image-list")]
975fn builder_set_image_list(builder: &TreeViewBuilder, out: &TreeView) {
976    if builder.image_list.is_some() {
977        out.set_image_list(builder.image_list);
978    }
979}
980
981#[cfg(not(feature = "image-list"))]
982fn builder_set_image_list(_builder: &TreeViewBuilder, _out: &TreeView) {}
983
984fn blank_item() -> TVITEMW {
985    TVITEMW {
986        mask: 0,
987        hItem: ptr::null_mut(),
988        state: 0,
989        stateMask: 0,
990        pszText: ptr::null_mut(),
991        cchTextMax: 0,
992        iImage: 0,
993        iSelectedImage: 0,
994        cChildren: 0,
995        lParam: 0,
996    }
997}