Skip to main content

native_windows_gui/controls/
date_picker.rs

1use winapi::um::winuser::{WS_VISIBLE, WS_DISABLED, WS_TABSTOP};
2use crate::win32::window_helper as wh;
3use crate::win32::base_helper::{to_utf16, check_hwnd};
4use crate::{Font, NwgError};
5use super::{ControlBase, ControlHandle};
6
7const NOT_BOUND: &'static str = "DatePicker is not yet bound to a winapi object";
8const BAD_HANDLE: &'static str = "INTERNAL ERROR: DatePicker handle is not HWND!";
9
10
11bitflags! {
12
13    /**
14        The DatePickerFlags flags
15
16        * NONE:     No flags. Equivalent to a invisible date picker.
17        * VISIBLE:  The date picker is immediatly visible after creation
18        * DISABLED: The date picker cannot be interacted with by the user. It also has a grayed out look.
19        * TAB_STOP: The control can be selected using tab navigation
20    */
21    pub struct DatePickerFlags: u32 {
22        const VISIBLE = WS_VISIBLE;
23        const DISABLED = WS_DISABLED;
24        const TAB_STOP = WS_TABSTOP;
25    }
26}
27
28/**
29    A date struct that can be passed to a date time picker control.
30    Fields are self explanatory.
31*/
32#[derive(Clone, Copy, PartialEq, Debug)]
33pub struct DatePickerValue {
34    pub year: u16,
35    pub month: u16,
36    pub day: u16
37}
38
39
40
41/**
42A date and time picker (DTP) control provides a simple and intuitive interface through which to exchange date and time information with a user.
43For example, with a DTP control you can ask the user to enter a date and then easily retrieve the selection.
44
45Requires the `datetime-picker` feature. 
46
47**Builder parameters:**
48  * `parent`:   **Required.** The dtp parent container.
49  * `size`:     The dtp size.
50  * `position`: The dtp position.
51  * `enabled`:  If the dtp can be used by the user. It also has a grayed out look if disabled.
52  * `flags`:    A combination of the DatePickerFlags values.
53  * `ex_flags`: A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
54  * `font`:     The font used for the dtp text
55  * `date`:     The default date as a `DatePickerValue` value
56  * `format`:   The format of the date. See the `set_format` method.
57  * `range`:    The accepted range of dates. The value is inclusive.
58  * `focus`:    The control receive focus after being created
59
60**Control events:**
61  * `OnDatePickerClosed`: When the datepicker dropdown is closed
62  * `OnDatePickerDropdown`: When the datepicker dropdown is opened
63  * `OnDatePickerChanged`: When a new value in a datepicker is choosen
64  * `MousePress(_)`: Generic mouse press events on the checkbox
65  * `OnMouseMove`: Generic mouse mouse event
66  * `OnMouseWheel`: Generic mouse wheel event
67
68```rust
69use native_windows_gui as nwg;
70fn build_dtp(date: &mut nwg::DatePicker, window: &nwg::Window) {
71    let v = nwg::DatePickerValue { year: 2000, month: 10, day: 5 };
72    let v1 = nwg::DatePickerValue { year: 2000, month: 10, day: 5 };
73    let v2 = nwg::DatePickerValue { year: 2012, month: 10, day: 5 };
74    
75    nwg::DatePicker::builder()
76        .size((200, 300))
77        .position((0, 0))
78        .date(Some(v))
79        .format(Some("'YEAR: 'yyyy"))
80        .range(Some([v1, v2]))
81        .parent(window)
82        .build(date);
83}
84```
85*/
86#[derive(Default, PartialEq, Eq)]
87pub struct DatePicker {
88    pub handle: ControlHandle
89}
90
91impl DatePicker {
92
93    pub fn builder<'a>() -> DatePickerBuilder<'a> {
94        DatePickerBuilder {
95            size: (100, 25),
96            position: (0, 0),
97            focus: false,
98            flags: None,
99            ex_flags: 0,
100            font: None,
101            parent: None,
102            date: None,
103            format: None,
104            range: None
105        }
106    }
107
108    /**
109        Sets the date format of the control
110
111        About the format string:  
112        - `d` 	    The one- or two-digit day.  
113        - `dd` 	    The two-digit day. Single-digit day values are preceded by a zero.  
114        - `ddd` 	The three-character weekday abbreviation.  
115        - `dddd` 	The full weekday name.  
116        - `M` 	    The one- or two-digit month number.  
117        - `MM` 	    The two-digit month number. Single-digit values are preceded by a zero.  
118        - `MMM` 	The three-character month abbreviation.  
119        - `MMMM` 	The full month name.  
120        - `t` 	    The one-letter AM/PM abbreviation (that is, AM is displayed as "A").  
121        - `tt` 	    The two-letter AM/PM abbreviation (that is, AM is displayed as "AM").  
122        - `yy` 	    The last two digits of the year (that is, 1996 would be displayed as "96").  
123        - `yyyy` 	The full year (that is, 1996 would be displayed as "1996").   
124
125        - `h` 	The one- or two-digit hour in 12-hour format.
126        - `hh` 	The two-digit hour in 12-hour format. Single-digit values are preceded by a zero.
127        - `H` 	The one- or two-digit hour in 24-hour format.
128        - `HH` 	The two-digit hour in 24-hour format. Single-digit values are preceded by a zero.
129        - `m` 	The one- or two-digit minute.
130        - `mm` 	The two-digit minute. Single-digit values are preceded by a zero.
131        
132        Furthermore, any string enclosed in `'` can be used in the format to display text.  
133        For example, to display the current date with the format `'Today is: Tuesday Mar 23, 1996`, the format string is `'Today is: 'dddd MMM dd', 'yyyy`. 
134    
135        If `format` is set to `None`, use the default system format.
136    */
137    pub fn set_format<'a>(&self, format: Option<&'a str>) {
138        use winapi::um::commctrl::DTM_SETFORMATW;
139        use winapi::shared::minwindef::LPARAM;
140
141        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
142
143        let (_format, format_ptr) = if format.is_some() {
144            let f = to_utf16(format.unwrap());
145            let fptr = f.as_ptr() as LPARAM;
146            (f, fptr)
147        } else {
148            (Vec::new(), 0)
149        };
150
151        wh::send_message(handle, DTM_SETFORMATW, 0, format_ptr);
152    }
153
154    /**
155        Return the check state of the checkbox of the control.  
156        If the date time picker is not optional, return false.
157
158        To set the check state of the control, use `set_value` method
159    */
160    pub fn checked(&self) -> bool {
161        use winapi::um::winuser::STATE_SYSTEM_CHECKED;
162
163        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
164
165        let info = unsafe{ get_dtp_info(handle) };
166
167        match info.stateCheck {
168            STATE_SYSTEM_CHECKED => true,
169            _ => false
170        }
171    }
172
173    /// Close the calendar popup if it is open. Note that there is no way to force the calendar to drop down
174    pub fn close_calendar(&self) {
175        use winapi::um::commctrl::DTM_CLOSEMONTHCAL;
176        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
177        wh::send_message(handle, DTM_CLOSEMONTHCAL, 0, 0);
178    }
179
180    /**
181        Return the time set in the control in a `PickerDate` structure.  
182        Return None if `optional` was set and the checkbox is not checked.  
183        Note: use `get_text` to get the text value of the control.
184    */
185    pub fn value(&self) -> Option<DatePickerValue> {
186        use winapi::um::commctrl::{GDT_VALID, DTM_GETSYSTEMTIME};
187        use winapi::um::minwinbase::SYSTEMTIME;
188        use std::mem;
189
190        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
191
192        let mut syst: SYSTEMTIME = unsafe{ mem::zeroed() };
193
194        let r = unsafe{ wh::send_message(handle, DTM_GETSYSTEMTIME, 0, mem::transmute(&mut syst)) };
195        match r {
196            GDT_VALID => Some(DatePickerValue {
197                year: syst.wYear,
198                month: syst.wMonth,
199                day: syst.wDay
200            }),
201            _ => None
202        }
203    }
204
205    /**
206        Set the time set in the control in a `PickerDate` structure.  
207        If `None` is passed, this clears the checkbox.
208    */
209    pub fn set_value(&self, date: Option<DatePickerValue>) {
210        use winapi::um::commctrl::{DTM_SETSYSTEMTIME, GDT_VALID, GDT_NONE};
211        use winapi::shared::minwindef::{WPARAM, LPARAM};
212        use winapi::um::minwinbase::SYSTEMTIME;
213
214        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
215
216        match date {
217            Some(date) => {
218                let syst: SYSTEMTIME = SYSTEMTIME{ 
219                    wYear: date.year, 
220                    wMonth: date.month, 
221                    wDay: date.day, 
222                    wDayOfWeek:0, wHour:0, wMinute:0, wSecond:0, wMilliseconds: 0 
223                };
224
225                wh::send_message(handle, DTM_SETSYSTEMTIME, GDT_VALID as WPARAM, &syst as *const SYSTEMTIME as LPARAM);
226            },
227            None => { 
228                wh::send_message(handle, DTM_SETSYSTEMTIME, GDT_NONE as WPARAM, 0); 
229            }
230        };
231    }
232
233    /// Gets the current minimum and maximum allowable system times for a date and time picker control.
234    pub fn range(&self) -> [DatePickerValue; 2] {
235        use winapi::um::commctrl::DTM_GETRANGE;
236        use winapi::um::minwinbase::SYSTEMTIME;
237        use winapi::shared::minwindef::{LPARAM};
238        use std::mem;
239
240        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
241
242        let mut tr: [SYSTEMTIME; 2] = unsafe { mem::zeroed() };
243
244        wh::send_message(handle, DTM_GETRANGE, 0, &mut tr as *mut [SYSTEMTIME; 2] as LPARAM); 
245    
246        [
247            DatePickerValue { year: tr[0].wYear, month: tr[0].wMonth, day: tr[0].wDay },
248            DatePickerValue { year: tr[1].wYear, month: tr[1].wMonth, day: tr[1].wDay },
249        ]
250        
251    }
252    
253    /// Sets the minimum and maximum allowable system times for a date and time picker control. 
254    pub fn set_range(&self, r: &[DatePickerValue; 2]) {
255        use winapi::um::commctrl::DTM_SETRANGE;
256        use winapi::um::commctrl::{GDTR_MIN, GDTR_MAX};
257        use winapi::um::minwinbase::SYSTEMTIME;
258        use winapi::shared::minwindef::{LPARAM};
259
260        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
261
262        let values = [
263            SYSTEMTIME { wYear: r[0].year , wMonth: r[0].month, wDayOfWeek: 0, wDay: r[0].day, wHour: 0, wMinute: 0, wSecond: 0, wMilliseconds: 0 },
264            SYSTEMTIME { wYear: r[1].year , wMonth: r[1].month, wDayOfWeek: 0, wDay: r[1].day, wHour: 0, wMinute: 0, wSecond: 0, wMilliseconds: 0 }
265        ];
266
267        wh::send_message(handle, DTM_SETRANGE, GDTR_MIN | GDTR_MAX, &values as *const [SYSTEMTIME; 2] as LPARAM);
268    }
269
270    /// Return the font of the control
271    pub fn font(&self) -> Option<Font> {
272        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
273        let font_handle = wh::get_window_font(handle);
274        if font_handle.is_null() {
275            None
276        } else {
277            Some(Font { handle: font_handle })
278        }
279    }
280
281    /// Sets the font of the control
282    pub fn set_font(&self, font: Option<&Font>) {
283        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
284        unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
285    }
286
287    /// Return true if the control currently has the keyboard focus
288    pub fn focus(&self) -> bool {
289        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
290        unsafe { wh::get_focus(handle) }
291    }
292
293    /// Sets the keyboard focus on the button.
294    pub fn set_focus(&self) {
295        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
296        unsafe { wh::set_focus(handle); }
297    }
298
299    /// Return true if the control user can interact with the control, return false otherwise
300    pub fn enabled(&self) -> bool {
301        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
302        unsafe { wh::get_window_enabled(handle) }
303    }
304
305    /// Enable or disable the control
306    pub fn set_enabled(&self, v: bool) {
307        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
308        unsafe { wh::set_window_enabled(handle, v) }
309    }
310
311    /// Return true if the control is visible to the user. Will return true even if the 
312    /// control is outside of the parent client view (ex: at the position (10000, 10000))
313    pub fn visible(&self) -> bool {
314        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
315        unsafe { wh::get_window_visibility(handle) }
316    }
317
318    /// Show or hide the control to the user
319    pub fn set_visible(&self, v: bool) {
320        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
321        unsafe { wh::set_window_visibility(handle, v) }
322    }
323
324    /// Return the size of the date picker in the parent window
325    pub fn size(&self) -> (u32, u32) {
326        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
327        unsafe { wh::get_window_size(handle) }
328    }
329
330    /// Set the size of the date picker in the parent window
331    pub fn set_size(&self, x: u32, y: u32) {
332        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
333        unsafe { wh::set_window_size(handle, x, y, false) }
334    }
335
336    /// Return the position of the date picker in the parent window
337    pub fn position(&self) -> (i32, i32) {
338        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
339        unsafe { wh::get_window_position(handle) }
340    }
341
342    /// Set the position of the date picker in the parent window
343    pub fn set_position(&self, x: i32, y: i32) {
344        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
345        unsafe { wh::set_window_position(handle, x, y) }
346    }
347
348
349    /// Winapi class name used during control creation
350    pub fn class_name(&self) -> &'static str {
351        "SysDateTimePick32"
352    }
353
354    /// Winapi base flags used during window creation
355    pub fn flags(&self) -> u32 {
356        WS_VISIBLE | WS_TABSTOP
357    }
358
359    /// Winapi flags required by the control
360    pub fn forced_flags(&self) -> u32 {
361        use winapi::um::winuser::{WS_CHILD};
362        use winapi::um::commctrl::DTS_SHOWNONE;
363
364        WS_CHILD | DTS_SHOWNONE
365    }
366
367}
368
369impl Drop for DatePicker {
370    fn drop(&mut self) {
371        self.handle.destroy();
372    }
373}
374
375pub struct DatePickerBuilder<'a> {
376    size: (i32, i32),
377    position: (i32, i32),
378    flags: Option<DatePickerFlags>,
379    ex_flags: u32,
380    font: Option<&'a Font>,
381    focus: bool,
382    parent: Option<ControlHandle>,
383    date: Option<DatePickerValue>,
384    format: Option<&'a str>,
385    range: Option<[DatePickerValue; 2]>
386}
387
388impl<'a> DatePickerBuilder<'a> {
389
390    pub fn flags(mut self, flags: DatePickerFlags) -> DatePickerBuilder<'a> {
391        self.flags = Some(flags);
392        self
393    }
394
395    pub fn ex_flags(mut self, flags: u32) -> DatePickerBuilder<'a> {
396        self.ex_flags = flags;
397        self
398    }
399
400    pub fn size(mut self, size: (i32, i32)) -> DatePickerBuilder<'a> {
401        self.size = size;
402        self
403    }
404
405    pub fn position(mut self, pos: (i32, i32)) -> DatePickerBuilder<'a> {
406        self.position = pos;
407        self
408    }
409
410    pub fn font(mut self, font: Option<&'a Font>) -> DatePickerBuilder<'a> {
411        self.font = font;
412        self
413    }
414
415    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> DatePickerBuilder<'a> {
416        self.parent = Some(p.into());
417        self
418    }
419
420    pub fn date(mut self, date: Option<DatePickerValue>) -> DatePickerBuilder<'a> {
421        self.date = date;
422        self
423    }
424
425    pub fn format(mut self, format: Option<&'a str>) -> DatePickerBuilder<'a> {
426        self.format = format;
427        self
428    }
429
430    pub fn range(mut self, range: Option<[DatePickerValue; 2]>) -> DatePickerBuilder<'a> {
431        self.range = range;
432        self
433    }
434
435    pub fn focus(mut self, focus: bool) -> DatePickerBuilder<'a> {
436        self.focus = focus;
437        self
438    }
439
440    pub fn build(self, out: &mut DatePicker) -> Result<(), NwgError> {
441        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
442
443        let parent = match self.parent {
444            Some(p) => Ok(p),
445            None => Err(NwgError::no_parent("DatePicker"))
446        }?;
447
448        *out = Default::default();
449
450        out.handle = ControlBase::build_hwnd()
451            .class_name(out.class_name())
452            .forced_flags(out.forced_flags())
453            .flags(flags)
454            .ex_flags(self.ex_flags)
455            .size(self.size)
456            .position(self.position)
457            .parent(Some(parent))
458            .build()?;
459
460        if self.font.is_some() {
461            out.set_font(self.font);
462        } else {
463            out.set_font(Font::global_default().as_ref());
464        }
465
466        if self.date.is_some() {
467            out.set_value(self.date)
468        }
469
470        if self.range.is_some() {
471            out.set_range(&self.range.unwrap());
472        }
473
474        if self.format.is_some() {
475            out.set_format(self.format);
476        }
477
478        if self.focus {
479            out.set_focus();
480        }
481
482        Ok(())
483    }
484
485}
486
487
488use winapi::um::commctrl::DATETIMEPICKERINFO;
489use winapi::shared::windef::HWND;
490
491unsafe fn get_dtp_info(handle: HWND) -> DATETIMEPICKERINFO {
492    use winapi::um::commctrl::DTM_GETDATETIMEPICKERINFO;
493    use winapi::shared::minwindef::DWORD;
494    use std::mem;
495
496    let mut dtp_info: DATETIMEPICKERINFO = mem::zeroed();
497    dtp_info.cbSize = mem::size_of::<DATETIMEPICKERINFO>() as DWORD;
498    wh::send_message(handle, DTM_GETDATETIMEPICKERINFO, 0, mem::transmute(&mut dtp_info));
499
500    dtp_info
501}