native_windows_gui2/controls/
list_box.rs

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