Skip to main content

native_windows_gui/controls/
list_box.rs

1use winapi::shared::windef::HWND;
2use winapi::shared::minwindef::{WPARAM, LPARAM};
3use winapi::um::winuser::{LBS_MULTIPLESEL, LBS_NOSEL, WS_VISIBLE, WS_DISABLED, WS_TABSTOP};
4use crate::win32::window_helper as wh;
5use crate::win32::base_helper::{to_utf16, from_utf16, check_hwnd};
6use crate::{Font, NwgError};
7use super::{ControlBase, ControlHandle};
8use std::cell::{Ref, RefMut, RefCell};
9use std::fmt::Display;
10use std::ops::Range;
11use std::mem;
12
13const NOT_BOUND: &'static str = "ListBox is not yet bound to a winapi object";
14const BAD_HANDLE: &'static str = "INTERNAL ERROR: ListBox handle is not HWND!";
15
16
17bitflags! {
18    /**
19        The listbox flags
20
21        * NONE:     No flags. Equivalent to a invisible listbox.
22        * VISIBLE:  The listbox is immediatly visible after creation
23        * DISABLED: The listbox cannot be interacted with by the user. It also has a grayed out look.
24        * MULTI_SELECT: It is possible for the user to select more than 1 item at a time
25        * NO_SELECT: It is impossible for the user to select the listbox items
26        * TAB_STOP: The control can be selected using tab navigation
27    */
28    pub struct ListBoxFlags: u32 {
29        const NONE = 0;
30        const VISIBLE = WS_VISIBLE;
31        const DISABLED = WS_DISABLED;
32        const MULTI_SELECT = LBS_MULTIPLESEL;
33        const NO_SELECT = LBS_NOSEL;
34        const TAB_STOP = WS_TABSTOP;
35    }
36}
37
38/**
39A list box is a control window that contains a simple list of items from which the user can choose.
40
41Requires the `list-box` feature. 
42
43**Builder parameters:**
44  * `parent`:          **Required.** The listbox parent container.
45  * `size`:            The listbox size.
46  * `position`:        The listbox position.
47  * `enabled`:         If the listbox can be used by the user. It also has a grayed out look if disabled.
48  * `focus`:           The control receive focus after being created
49  * `flags`:           A combination of the ListBoxFlags values.
50  * `ex_flags`:        A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
51  * `font`:            The font used for the listbox text
52  * `collection`:      The default collections of the listbox
53  * `selected_index`:  The default selected index in the listbox collection
54  * `multi_selection`: The collections of indices to set as selected in a multi selection listbox 
55
56**Control events:**
57  * `OnListBoxSelect`: When the current listbox selection is changed
58  * `OnListBoxDoubleClick`: When a listbox item is clicked twice rapidly
59  * `MousePress(_)`: Generic mouse press events on the listbox
60  * `OnMouseMove`: Generic mouse mouse event
61  * `OnMouseWheel`: Generic mouse wheel event
62
63```rust
64use native_windows_gui as nwg;
65fn build_listbox(listb: &mut nwg::ListBox<&'static str>, window: &nwg::Window, font: &nwg::Font) {
66    nwg::ListBox::builder()
67        .flags(nwg::ListBoxFlags::VISIBLE | nwg::ListBoxFlags::MULTI_SELECT)
68        .collection(vec!["Hello", "World", "!!!!"])
69        .multi_selection(vec![0, 1, 2])
70        .font(Some(font))
71        .parent(window)
72        .build(listb);
73}
74```
75
76*/
77#[derive(Default)]
78pub struct ListBox<D: Display+Default> {
79    pub handle: ControlHandle,
80    collection: RefCell<Vec<D>>
81}
82
83impl<D: Display+Default> ListBox<D> {
84
85    pub fn builder<'a>() -> ListBoxBuilder<'a, D> {
86        ListBoxBuilder {
87            size: (100, 300),
88            position: (0, 0),
89            enabled: true,
90            focus: false,
91            flags: None,
92            ex_flags: 0,
93            font: None,
94            collection: None,
95            selected_index: None,
96            multi_selection: Vec::new(),
97            parent: None
98        }
99    }
100
101    /// Add a new item to the listbox. Sort the collection if the listbox is sorted.
102    pub fn push(&self, item: D) {
103        use winapi::um::winuser::LB_ADDSTRING;
104
105        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
106        let display = format!("{}", item);
107        let display_os = to_utf16(&display);
108
109        unsafe {
110            wh::send_message(handle, LB_ADDSTRING, 0, mem::transmute(display_os.as_ptr()));
111        }
112
113        self.collection.borrow_mut().push(item);
114    }
115
116    /// Insert an item in the collection and the control. 
117    ///
118    /// SPECIAL behaviour! If index is `std::usize::MAX`, the item is added at the end of the collection.
119    /// The method will still panic if `index > len` with every other values.
120    pub fn insert(&self, index: usize, item: D) {
121        use winapi::um::winuser::LB_INSERTSTRING;
122
123        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
124        let display = format!("{}", item);
125        let display_os = to_utf16(&display);
126
127        let mut col = self.collection.borrow_mut();
128        if index == std::usize::MAX {
129            col.push(item);
130        } else {
131            col.insert(index, item);
132        }
133
134        unsafe {
135            wh::send_message(handle, LB_INSERTSTRING, index, mem::transmute(display_os.as_ptr()));
136        }
137    }
138
139
140    /// Remove the item at the selected index and returns it.
141    /// Panic of the index is out of bounds
142    pub fn remove(&self, index: usize) -> D {
143        use winapi::um::winuser::LB_DELETESTRING;
144
145        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
146        wh::send_message(handle, LB_DELETESTRING, index as WPARAM, 0);
147
148        let mut col_ref = self.collection.borrow_mut();
149        col_ref.remove(index)
150    }
151
152    /// Return the index of the currencty selected item for single value list box.
153    /// Return `None` if no item is selected.
154    pub fn selection(&self) -> Option<usize> {
155        use winapi::um::winuser::{LB_GETCURSEL, LB_ERR};
156
157        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
158        let index = wh::send_message(handle, LB_GETCURSEL , 0, 0);
159
160        if index == LB_ERR { None }
161        else { Some(index as usize) }
162    }
163
164    /// Return the number of selected item in the list box
165    /// Returns 0 for single select list box
166    pub fn multi_selection_len(&self) -> usize {
167        use winapi::um::winuser::{LB_GETSELCOUNT, LB_ERR};
168
169        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
170        match wh::send_message(handle, LB_GETSELCOUNT, 0, 0) {
171            LB_ERR => 0,
172            value => value as usize
173        }
174    }
175
176    /// Return a list index
177    /// Returns an empty vector for single select list box.
178    pub fn multi_selection(&self) -> Vec<usize> {
179        use winapi::um::winuser::{LB_GETSELCOUNT, LB_GETSELITEMS, LB_ERR};
180
181        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
182        let select_count = match wh::send_message(handle, LB_GETSELCOUNT, 0, 0) {
183            LB_ERR => usize::max_value(),
184            value => value as usize
185        };
186
187        if select_count == usize::max_value() || usize::max_value() == 0 {
188            return Vec::new();
189        }
190
191        let mut indices_buffer: Vec<u32> = Vec::with_capacity(select_count);
192        unsafe { indices_buffer.set_len(select_count) };
193
194        wh::send_message(
195            handle,
196            LB_GETSELITEMS,
197            select_count as WPARAM,
198            indices_buffer.as_mut_ptr() as LPARAM
199        );
200
201        indices_buffer.into_iter().map(|i| i as usize).collect()
202    }
203
204    /// Return the display value of the currenctly selected item for single value
205    /// Return `None` if no item is selected. This reads the visual value.
206    pub fn selection_string(&self) -> Option<String> {
207        use winapi::um::winuser::{LB_GETCURSEL, LB_GETTEXTLEN, LB_GETTEXT, LB_ERR};
208        use winapi::shared::ntdef::WCHAR;
209
210        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
211        let index = wh::send_message(handle, LB_GETCURSEL, 0, 0);
212
213        if index == LB_ERR { None }
214        else {
215            let index = index as usize;
216            let length = (wh::send_message(handle, LB_GETTEXTLEN, index, 0) as usize) + 1;  // +1 for the terminating null character
217            let mut buffer: Vec<WCHAR> = Vec::with_capacity(length);
218            unsafe { 
219                buffer.set_len(length); 
220                wh::send_message(handle, LB_GETTEXT, index, mem::transmute(buffer.as_ptr()));
221            }
222
223            Some(from_utf16(&buffer))
224        }
225    }
226
227    /// Set the currently selected item in the list box for single value list box.
228    /// Does nothing if the index is out of bound
229    /// If the value is None, remove the selected value
230    pub fn set_selection(&self, index: Option<usize>) {
231        use winapi::um::winuser::LB_SETCURSEL;
232
233        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
234        let index = index.unwrap_or(-1isize as usize);
235        wh::send_message(handle, LB_SETCURSEL, index, 0);
236    }
237
238    /// Select the item as index `index` in a multi item list box
239    pub fn multi_add_selection(&self, index: usize) {
240        use winapi::um::winuser::LB_SETSEL;
241        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
242        wh::send_message(handle, LB_SETSEL, 1, index as LPARAM);
243    }
244
245    /// Unselect the item as index `index` in a multi item list box
246    pub fn multi_remove_selection(&self, index: usize) {
247        use winapi::um::winuser::LB_SETSEL;
248        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
249        wh::send_message(handle, LB_SETSEL, 0, index as LPARAM);
250    }
251
252    /// Unselect every item in the list box
253    pub fn unselect_all(&self) {
254        use winapi::um::winuser::LB_SETSEL;
255        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
256        wh::send_message(handle, LB_SETSEL, 0, -1);
257    }
258
259    /// Select every item in the list box
260    pub fn select_all(&self) {
261        use winapi::um::winuser::LB_SETSEL;
262        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
263        wh::send_message(handle, LB_SETSEL, 1, -1);
264    }
265
266    /// Select a range of items in a multi list box
267    pub fn multi_select_range(&self, range: Range<usize>) {
268        use winapi::um::winuser::LB_SELITEMRANGEEX;
269
270        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
271        let start = range.start as WPARAM;
272        let end = range.end as LPARAM;
273        wh::send_message(handle, LB_SELITEMRANGEEX, start, end);
274    }
275
276    /// Unselect a range of items in a multi list box
277    pub fn multi_unselect_range(&self, range: Range<usize>) {
278        use winapi::um::winuser::LB_SELITEMRANGEEX;
279
280        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
281        let start = range.start as LPARAM;
282        let end = range.end as WPARAM;
283        wh::send_message(handle, LB_SELITEMRANGEEX, end, start);
284    }
285
286    /// Search an item that begins by the value and select the first one found.
287    /// The search is not case sensitive, so this string can contain any combination of uppercase and lowercase letters.
288    /// Return the index of the selected string or None if the search was not successful
289    pub fn set_selection_string(&self, value: &str) -> Option<usize> {
290        use winapi::um::winuser::{LB_SELECTSTRING, LB_ERR};
291
292        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
293        let os_string = to_utf16(value);
294
295        unsafe {
296            let index = wh::send_message(handle, LB_SELECTSTRING, 0, mem::transmute(os_string.as_ptr()));
297            if index == LB_ERR {
298                None
299            } else {
300                Some(index as usize)
301            }
302        }
303    }
304
305    /// Check if the item at `index` is selected by the user
306    /// Return `false` if the index is out of range.
307    pub fn selected(&self, index: usize) -> bool {
308        use winapi::um::winuser::LB_GETSEL;
309
310        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
311        wh::send_message(handle, LB_GETSEL, index as WPARAM, 0) > 0
312    }
313
314    /// Update the visual of the control with the inner collection.
315    /// This rebuild every item in the list box and can take some time on big collections.
316    pub fn sync(&self) {
317        use winapi::um::winuser::{LB_ADDSTRING, LB_INITSTORAGE};
318
319        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
320
321        self.clear_inner(handle);
322
323        let item_count = self.collection.borrow().len();
324        wh::send_message(handle, LB_INITSTORAGE, item_count as WPARAM, (10*item_count) as LPARAM);
325
326        for item in self.collection.borrow().iter() {
327            let display = format!("{}", item);
328            let display_os = to_utf16(&display);
329            
330            unsafe {
331                wh::send_message(handle, LB_ADDSTRING, 0, mem::transmute(display_os.as_ptr()));
332            }
333        }
334    }
335
336    /// Set the item collection of the list box. Return the old collection
337    pub fn set_collection(&self, mut col: Vec<D>) -> Vec<D> {
338        use winapi::um::winuser::LB_ADDSTRING;
339
340        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
341
342        self.clear_inner(handle);
343
344        for item in col.iter() {
345            let display = format!("{}", item);
346            let display_os = to_utf16(&display);
347            
348            unsafe {
349                wh::send_message(handle, LB_ADDSTRING, 0, mem::transmute(display_os.as_ptr()));
350            }
351        }
352
353        let mut col_ref = self.collection.borrow_mut();
354        mem::swap::<Vec<D>>(&mut col_ref, &mut col);
355
356        col
357    }
358
359    /// Clears the control and free the underlying collection. Same as `set_collection(Vec::new())`
360    pub fn clear(&self) {
361        self.set_collection(Vec::new());
362    }
363
364    /// Return the number of items in the control. NOT the inner rust collection
365    pub fn len(&self) -> usize {
366        use winapi::um::winuser::LB_GETCOUNT;
367        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
368        wh::send_message(handle, LB_GETCOUNT, 0, 0) as usize
369    }
370
371
372    //
373    // Common control functions
374    //
375
376    /// Return the font of the control
377    pub fn font(&self) -> Option<Font> {
378        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
379
380        let font_handle = wh::get_window_font(handle);
381        if font_handle.is_null() {
382            None
383        } else {
384            Some(Font { handle: font_handle })
385        }
386    }
387
388    /// Set the font of the control
389    pub fn set_font(&self, font: Option<&Font>) {
390        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
391        unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
392    }
393
394    /// Return true if the control currently has the keyboard focus
395    pub fn focus(&self) -> bool {
396        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
397        unsafe { wh::get_focus(handle) }
398    }
399
400    /// Set the keyboard focus on the button.
401    pub fn set_focus(&self) {
402        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
403        unsafe { wh::set_focus(handle); }
404    }
405
406    /// Return true if the control user can interact with the control, return false otherwise
407    pub fn enabled(&self) -> bool {
408        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
409        unsafe { wh::get_window_enabled(handle) }
410    }
411
412    /// Enable or disable the control
413    pub fn set_enabled(&self, v: bool) {
414        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
415        unsafe { wh::set_window_enabled(handle, v) }
416    }
417
418    /// Return true if the control is visible to the user. Will return true even if the 
419    /// control is outside of the parent client view (ex: at the position (10000, 10000))
420    pub fn visible(&self) -> bool {
421        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
422        unsafe { wh::get_window_visibility(handle) }
423    }
424
425    /// Show or hide the control to the user
426    pub fn set_visible(&self, v: bool) {
427        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
428        unsafe { wh::set_window_visibility(handle, v) }
429    }
430
431    /// Return the size of the button in the parent window
432    pub fn size(&self) -> (u32, u32) {
433        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
434        unsafe { wh::get_window_size(handle) }
435    }
436
437    /// Set the size of the button in the parent window
438    pub fn set_size(&self, x: u32, y: u32) {
439        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
440        unsafe { wh::set_window_size(handle, x, y, false) }
441    }
442
443    /// Return the position of the button in the parent window
444    pub fn position(&self) -> (i32, i32) {
445        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
446        unsafe { wh::get_window_position(handle) }
447    }
448
449    /// Set the position of the button in the parent window
450    pub fn set_position(&self, x: i32, y: i32) {
451        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
452        unsafe { wh::set_window_position(handle, x, y) }
453    }
454
455    /// Get read-only access to the inner collection of the list box
456    /// This call refcell.borrow under the hood. Be sure to drop the value before
457    /// calling other list box methods
458    pub fn collection(&self) -> Ref<Vec<D>> {
459        self.collection.borrow()
460    }
461
462    /// Get mutable access to the inner collection of the list box. Does not update the visual
463    /// control. Call `sync` to update the view. This call refcell.borrow_mut under the hood. 
464    /// Be sure to drop the value before calling other list box methods
465    pub fn collection_mut(&self) -> RefMut<Vec<D>> {
466        self.collection.borrow_mut()
467    }
468
469    /// Winapi class name used during control creation
470    pub fn class_name(&self) -> &'static str {
471        "ListBox"
472    }
473
474    /// Winapi base flags used during window creation
475    pub fn flags(&self) -> u32 {
476        WS_VISIBLE | WS_TABSTOP
477    }
478
479    /// Winapi flags required by the control
480    pub fn forced_flags(&self) -> u32 {
481        use winapi::um::winuser::{LBS_HASSTRINGS, WS_BORDER, WS_VSCROLL, LBS_NOTIFY, WS_CHILD};
482
483        LBS_HASSTRINGS | LBS_NOTIFY | WS_BORDER  | WS_CHILD | WS_VSCROLL
484    }
485
486    /// Remove all value displayed in the control without touching the rust collection
487    fn clear_inner(&self, handle: HWND) {
488        use winapi::um::winuser::LB_RESETCONTENT;
489        wh::send_message(handle, LB_RESETCONTENT, 0, 0);
490    }
491
492}
493
494impl<D: Display+Default> Drop for ListBox<D> {
495    fn drop(&mut self) {
496        self.handle.destroy();
497    }
498}
499
500pub struct ListBoxBuilder<'a, D: Display+Default> {
501    size: (i32, i32),
502    position: (i32, i32),
503    enabled: bool,
504    focus: bool,
505    flags: Option<ListBoxFlags>,
506    ex_flags: u32,
507    font: Option<&'a Font>,
508    collection: Option<Vec<D>>,
509    selected_index: Option<usize>,
510    multi_selection: Vec<usize>,
511    parent: Option<ControlHandle>
512}
513
514impl<'a, D: Display+Default> ListBoxBuilder<'a, D> {
515
516    pub fn flags(mut self, flags: ListBoxFlags) -> ListBoxBuilder<'a, D> {
517        self.flags = Some(flags);
518        self
519    }
520
521    pub fn ex_flags(mut self, flags: u32) -> ListBoxBuilder<'a, D> {
522        self.ex_flags = flags;
523        self
524    }
525
526    pub fn size(mut self, size: (i32, i32)) -> ListBoxBuilder<'a, D> {
527        self.size = size;
528        self
529    }
530
531    pub fn position(mut self, pos: (i32, i32)) -> ListBoxBuilder<'a, D> {
532        self.position = pos;
533        self
534    }
535
536    pub fn font(mut self, font: Option<&'a Font>) -> ListBoxBuilder<'a, D> {
537        self.font = font;
538        self
539    }
540
541    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> ListBoxBuilder<'a, D> {
542        self.parent = Some(p.into());
543        self
544    }
545
546    pub fn collection(mut self, collection: Vec<D>) -> ListBoxBuilder<'a, D> {
547        self.collection = Some(collection);
548        self
549    }
550
551    pub fn selected_index(mut self, index: Option<usize>) -> ListBoxBuilder<'a, D> {
552        self.selected_index = index;
553        self
554    }
555
556    pub fn multi_selection(mut self, select: Vec<usize>) -> ListBoxBuilder<'a, D> {
557        self.multi_selection = select;
558        self
559    }
560
561    pub fn enabled(mut self, enabled: bool) -> ListBoxBuilder<'a, D> {
562        self.enabled = enabled;
563        self
564    }
565
566    pub fn focus(mut self, focus: bool) -> ListBoxBuilder<'a, D> {
567        self.focus = focus;
568        self
569    }
570
571    pub fn build(self, out: &mut ListBox<D>) -> Result<(), NwgError> {
572        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
573
574        let parent = match self.parent {
575            Some(p) => Ok(p),
576            None => Err(NwgError::no_parent("ListBox"))
577        }?;
578
579        *out = Default::default();
580
581        out.handle = ControlBase::build_hwnd()
582            .class_name(out.class_name())
583            .forced_flags(out.forced_flags())
584            .flags(flags)
585            .ex_flags(self.ex_flags)
586            .size(self.size)
587            .position(self.position)
588            .parent(Some(parent))
589            .build()?;
590
591        if self.font.is_some() {
592            out.set_font(self.font);
593        } else {
594            out.set_font(Font::global_default().as_ref());
595        }
596
597        if let Some(col) = self.collection {
598            out.set_collection(col);
599        }
600
601        if flags & LBS_MULTIPLESEL == LBS_MULTIPLESEL {
602            for i in self.multi_selection {
603                out.multi_add_selection(i);
604            }
605        } else {
606            out.set_selection(self.selected_index);
607        }
608
609        if self.focus {
610            out.set_focus();
611        }
612
613        if !self.enabled {
614            out.set_enabled(self.enabled);
615        }
616
617        Ok(())
618    }
619
620}
621
622impl<D: Display+Default> PartialEq for ListBox<D> {
623    fn eq(&self, other: &Self) -> bool {
624        self.handle == other.handle
625    }
626}