win_wrap/
control.rs

1/*
2 * Copyright (c) 2024. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use std::{
15    ffi::CString,
16    fmt::{Debug, Formatter},
17};
18
19pub use windows::Win32::UI::WindowsAndMessaging::{
20    PRF_CHECKVISIBLE, PRF_CHILDREN, PRF_CLIENT, PRF_ERASEBKGND, PRF_NONCLIENT, PRF_OWNED, WM_CLEAR,
21    WM_CLOSE, WM_COPY, WM_CUT, WM_GETTEXT, WM_GETTEXTLENGTH, WM_PAINT, WM_PASTE, WM_PRINT,
22    WM_SETREDRAW, WM_SETTEXT, WM_UNDO,
23};
24
25use crate::{
26    common::{HANDLE, HWND, LPARAM, LRESULT, WPARAM},
27    ext::StringExt,
28    graphic::HDC,
29    memory::InProcessMemory,
30    message::{send_message_timeout, SMTO_ABORTIFHUNG},
31    threading::get_process_handle_from_hwnd,
32};
33
34pub mod edit;
35
36/// 窗口控件
37pub struct WindowControl {
38    h_wnd: HWND,
39    h_process: HANDLE,
40}
41
42impl WindowControl {
43    /**
44    获取进程句柄。
45    */
46    pub fn get_process_handle(&self) -> HANDLE {
47        self.h_process
48    }
49
50    /**
51    把消息发送到控件。
52    `msg` 消息值。
53    `wp` 参数1。
54    `lp` 参数2。
55    */
56    pub fn send_message(&self, msg: u32, wp: WPARAM, lp: LPARAM) -> (LRESULT, usize) {
57        send_message_timeout(self.h_wnd, msg, wp, lp, SMTO_ABORTIFHUNG, 500)
58    }
59}
60
61impl From<HWND> for WindowControl {
62    fn from(value: HWND) -> Self {
63        let process_handle = get_process_handle_from_hwnd(value);
64        Self {
65            h_wnd: value,
66            h_process: process_handle,
67        }
68    }
69}
70
71impl Debug for WindowControl {
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        if let Some(text) = self.get_text() {
74            write!(f, "WindowControl(text:{})", text)
75        } else {
76            write!(f, "WindowControl()")
77        }
78    }
79}
80
81impl WindowControl {
82    //noinspection SpellCheckingInspection
83    /**
84    def_window_proc 函数将与窗口关联的文本复制到指定的缓冲区中,并返回复制的字符数。
85    请注意,对于非文本静态控件,这会提供最初创建控件时使用的文本,即 ID 号。
86    但是,它提供最初创建的非文本静态控件的 ID。 也就是说,如果随后使用 STM_SETIMAGE 对其进行更改,则仍会返回原始 ID。
87    对于编辑控件,要复制的文本是编辑控件的内容。
88    对于组合框,文本是编辑控件 (或静态文本) 组合框部分的内容。
89    对于按钮,文本是按钮名称。
90    对于其他窗口,文本是窗口标题。
91    若要复制列表框中项的文本,应用程序可以使用 LB_GETTEXT 消息。
92    将 WM_GETTEXT 消息发送到具有 SS_ICON 样式的静态控件时, 将在 lParam 指向的缓冲区前四个字节中返回图标的句柄。
93    仅当 WM_SETTEXT 消息已用于设置图标时,才如此。
94    富编辑: 如果要复制的文本超过 64K,请使用 EM_STREAMOUT 或 EM_GETSELTEXT 消息。
95    向非文本静态控件(如静态位图或静态图标控件)发送 WM_GETTEXT 消息不会返回字符串值。相反,它返回零。
96    此外,在早期版本的 Windows 中,应用程序可以将 WM_GETTEXT 消息发送到非文本静态控件,以检索控件的 ID。
97    若要检索控件的 ID,应用程序可以使用 get_window_long 传递GWL_ID作为索引值,或使用 GWLP_ID传递 get_window_long_ptr。
98    */
99    pub fn get_text(&self) -> Option<String> {
100        let (_, len) = self.send_message(WM_GETTEXTLENGTH, WPARAM::default(), LPARAM::default());
101        let mem = InProcessMemory::new(self.get_process_handle(), len * 2 + 1).unwrap();
102        self.send_message(
103            WM_GETTEXT,
104            WPARAM(len * 2 + 1),
105            LPARAM(mem.as_ptr() as isize),
106        );
107        mem.read(|buf| (buf as *const u16).to_string_utf16())
108    }
109
110    //noinspection SpellCheckingInspection
111    /**
112    允许重绘该窗口中的更改,或防止重绘该窗口中的更改。
113    如果应用程序处理此消息,则它应返回 0。
114    如果应用程序必须向列表框添加多个项,则此消息可能很有用。
115    应用程序可以在 `enabled` 设置为 false 的情况下调用此消息,添加项,然后在 `enabled` 设置为 true 的情况下再次调用该消息。
116    最后,应用程序可以调用 redraw_window (hWnd、 NULL、 NULL RDW_ERASE |RDW_FRAME |RDW_INVALIDATE |RDW_ALLCHILDREN) 会导致重新绘制列表框。
117    应将 redraw_window 与指定的标志一起使用,而不是 invalidate_rect,因为前者对于某些控件而言是必需的,这些控件具有自己的非工作区,或者具有导致它们获得非工作区 ((如 WS_THICKFRAME、 WS_BORDER 或 WS_EX_CLIENTEDGE) )。
118    如果控件没有非工作区,则带这些标志的 redraw_window 将只执行 与 invalidate_rect 一样多的失效。
119    当 `enabled` 设置为 false 时,将WM_SETREDRAW消息传递给 def_window_proc 函数会从窗口中删除WS_VISIBLE样式。
120    尽管窗口内容在屏幕上保持可见,但在此状态下的窗口上调用 is_window_visible 函数时返回 FALSE 。
121    当 `enabled` 设置为 true 时,将WM_SETREDRAW消息传递给 def_window_proc 函数会将WS_VISIBLE样式添加到窗口(如果未设置)。
122    如果应用程序将 `enabled` 设置为 true的WM_SETREDRAW消息发送到隐藏窗口,则该窗口将变为可见。
123    Windows 10 及更高版本;Windows Server 2016 及更高版本。
124    系统在窗口上设置名为 SysSetRedraw 的属性,该窗口的窗口过程将 WM_SETREDRAW 消息传递给 def_window_proc。
125    可以使用 get_prop 函数获取属性值(如果可用)。
126    禁用重绘时,get_prop 返回非零值。
127    启用重绘或窗口属性不存在时,get_prop 将返回零。
128    `enabled` 重绘状态。 如果此参数为 true,则可以在更改后重绘内容。 如果此参数为 false,则更改后无法重绘内容。
129    */
130    pub fn set_redraw(&self, enabled: bool) -> usize {
131        let enabled = if enabled { WPARAM(1) } else { WPARAM(0) };
132        let (_, res) = self.send_message(WM_SETREDRAW, enabled, LPARAM::default());
133        res
134    }
135
136    //noinspection SpellCheckingInspection
137    /**
138    设置窗口的文本。
139    如果设置了文本,则返回值为 true 。如果没有足够的空间来设置编辑控件中的文本,则为 FALSE (编辑控件) , LB_ERRSPACE (列表框);CB_ERRSPACE (组合框)。 如果此消息发送到没有编辑控件的组合框,则会 CB_ERR 。
140    def_window_proc 函数设置并显示窗口文本。
141    对于编辑控件,文本是编辑控件的内容。
142    对于组合框,文本是组合框编辑控件部分的内容。
143    对于按钮,文本是按钮名称。
144    对于其他窗口,文本是窗口标题。
145    此消息不会更改组合框的列表框中的当前选择。
146    应用程序应使用 CB_SELECTSTRING 消息在列表框中选择与编辑控件中的文本匹配的项。
147    */
148    pub fn set_text(&self, text: &str) -> bool {
149        let text = CString::new(text).unwrap();
150        let (_, res) = self.send_message(
151            WM_SETTEXT,
152            WPARAM::default(),
153            LPARAM(text.as_ptr() as isize),
154        );
155        res != 0
156    }
157
158    //noinspection SpellCheckingInspection
159    /**
160    当系统或其他应用程序请求绘制应用程序窗口的一部分时,将发送 WM_PAINT 消息。
161    调用 update_window 或 redraw_window 函数时发送消息,当应用程序使用 get_message 或 peek_message 函数获取WM_PAINT消息时,将发送该消息。
162    窗口通过其 WindowProc 函数接收此消息。
163    如果应用程序处理此消息,则返回零。
164    WM_PAINT消息由系统生成,不应由应用程序发送。
165    若要强制窗口绘制到特定设备上下文中,请使用 WM_PRINT 或 WM_PRINTCLIENT 消息。
166    请注意,这需要目标窗口支持 WM_PRINTCLIENT 消息。
167    大多数常用控件支持 WM_PRINTCLIENT 消息。
168    def_window_proc 函数验证更新区域。
169    如果必须绘制窗口框架,函数还可以将 WM_NCPAINT 消息发送到窗口过程,如果必须擦除窗口背景,则发送 WM_ERASEBKGND 消息。
170    当应用程序的消息队列中没有其他消息时,系统会发送此消息。
171    dispatch_message 确定消息的发送位置;
172    get_message 确定要调度的消息。
173    当应用程序的消息队列中没有其他消息时,get_message 将返回WM_PAINT消息,并且 dispatch_message 会将消息发送到相应的窗口过程。
174    由于调用 redraw_window 并设置了RDW_INTERNALPAINT标志,窗口可能会收到内部绘制消息。
175    在这种情况下,窗口可能不包含更新区域。
176    应用程序可以调用 get_update_rect 函数来确定窗口是否具有更新区域。
177    如果 get_update_rect 返回零,则应用程序无需调用 begin_paint 和 end_paint 函数。
178    应用程序必须通过查看每个WM_PAINT消息的内部数据结构来检查任何必要的内部绘制,因为WM_PAINT消息可能是由非 NULL 更新区域和调用 redraw_window 以及设置了RDW_INTERNALPAINT标志的 redraw_window 引起的。
179    系统仅发送一次内部 WM_PAINT 消息。
180    从 get_message 或 peek_message 返回内部WM_PAINT消息或由 update_window 发送到窗口后,系统不会发布或发送进一步WM_PAINT消息,直到窗口失效或重新调用 redraw_window 并设置RDW_INTERNALPAINT标志。
181    对于某些常见控件,默认 WM_PAINT 消息处理会检查 wParam 参数。
182    如果 wParam 为非 NULL,则控件假定该值为 HDC,并使用该设备上下文进行绘制。
183    */
184    pub fn paint(&self) -> usize {
185        let (_, res) = self.send_message(WM_PAINT, WPARAM::default(), LPARAM::default());
186        res
187    }
188
189    //noinspection SpellCheckingInspection
190    /**
191    WM_PRINT消息将发送到窗口,请求它在指定的设备上下文(最常见的是打印机设备上下文)中绘制自身。
192    窗口通过其 WindowProc 函数接收此消息。
193    `h_dc` 要绘制的设备上下文的句柄。
194    `options` 绘图选项。 此参数可使用以下一个或多个值。
195    - PRF_CHECKVISIBLE 仅当窗口可见时,才会绘制该窗口。
196    - PRF_CHILDREN 绘制所有可见子窗口。
197    - PRF_CLIENT 绘制窗口的工作区。
198    - PRF_ERASEBKGND 在绘制窗口之前擦除背景。
199    - PRF_NONCLIENT 绘制窗口的非工作区。
200    - PRF_OWNED 绘制所有拥有的窗口。
201    def_window_proc 函数根据指定的绘图选项处理此消息:如果指定了PRF_CHECKVISIBLE并且窗口不可见,则不执行任何操作,如果指定了PRF_NONCLIENT,则绘制指定设备上下文中的非工作区,如果指定了PRF_ERASEBKGND,则向窗口发送WM_ERASEBKGND消息,如果指定了PRF_CLIENT, 向窗口发送WM_PRINTCLIENT消息,如果设置了PRF_CHILDREN,则向每个可见子窗口发送一条WM_PRINT消息,如果设置了PRF_OWNED,则向每个可见的拥有窗口发送一条WM_PRINT消息。
202    */
203    pub fn print(&self, h_dc: HDC, options: i32) -> usize {
204        let (_, res) =
205            self.send_message(WM_PRINT, WPARAM(h_dc.0 as usize), LPARAM(options as isize));
206        res
207    }
208    /**
209    作为窗口或应用程序应终止的信号发送。
210    窗口通过其 WindowProc 函数接收此消息。
211    如果应用程序处理此消息,则它应返回零。
212    应用程序可以在销毁窗口之前提示用户进行确认,方法是处理 WM_CLOSE 消息,并仅在用户确认选择时调用 destroy_window 函数。
213    默认情况下, def_window_proc 函数调用 destroy_window 函数来销毁窗口。
214    */
215    pub fn close(&self) -> usize {
216        let (_, res) = self.send_message(WM_CLOSE, WPARAM::default(), LPARAM::default());
217        res
218    }
219
220    //noinspection SpellCheckingInspection
221    /**
222    应用程序将 WM_CUT 消息发送到编辑控件或组合框,以删除 (剪切) 编辑控件中的当前选定内容(如果有),并将已删除的文本以 CF_TEXT 格式复制到剪贴板。
223    可以通过向编辑控件发送EM_UNDO消息来撤消WM_CUT消息执行的删除操作。
224    若要删除当前选定内容而不将已删除的文本放在剪贴板上,请使用 WM_CLEAR 消息。
225    发送到组合框时, WM_CUT 消息由其编辑控件处理。 发送到具有 CBS_DROPDOWNLIST 样式的组合框时,此消息无效。
226    */
227    pub fn cut(&self) {
228        self.send_message(WM_CUT, WPARAM::default(), LPARAM::default());
229    }
230
231    //noinspection SpellCheckingInspection
232    /**
233    此消息由应用程序发送到编辑控件或组合框,以CF_UNICODETEXT格式将当前选定内容复制到剪贴板。
234    当发送到组合框时,WM_COPY消息由其编辑控件处理。此消息在发送到具有CBS_DROPDOWNLIST样式的组合框时不起作用。
235    */
236    pub fn copy(&self) {
237        self.send_message(WM_COPY, WPARAM::default(), LPARAM::default());
238    }
239
240    //noinspection SpellCheckingInspection
241    /**
242    应用程序将 WM_PASTE 消息发送到编辑控件或组合框,以将剪贴板的当前内容复制到位于当前插入点位置的编辑控件。 仅当剪贴板包含 CF_TEXT 格式的数据时,才会插入数据。
243    发送到组合框时, WM_PASTE 消息由其编辑控件处理。 发送到具有 CBS_DROPDOWNLIST 样式的组合框时,此消息无效。
244    */
245    pub fn paste(&self) {
246        self.send_message(WM_PASTE, WPARAM::default(), LPARAM::default());
247    }
248
249    //noinspection SpellCheckingInspection
250    /**
251    应用程序将 WM_CLEAR 消息发送到编辑控件或组合框,以从编辑控件中删除 (清除) 当前选择(如果有)。
252    可以通过向编辑控件发送EM_UNDO消息来撤消WM_CLEAR消息执行的删除操作。
253    若要删除当前选定内容并将已删除的内容放在剪贴板上,请使用 WM_CUT 消息。
254    发送到组合框时, WM_CLEAR 消息由其编辑控件处理。 发送到具有 CBS_DROPDOWNLIST 样式的组合框时,此消息不起作用。
255    */
256    pub fn clear(&self) {
257        self.send_message(WM_CLEAR, WPARAM::default(), LPARAM::default());
258    }
259
260    /**
261    应用程序将 WM_UNDO 消息发送到编辑控件以撤消最后一个操作。 将此消息发送到编辑控件时,将还原以前删除的文本或删除以前添加的文本。
262    如果消息成功,则返回值为 true。如果消息失败,则返回值为 false。
263    Rich Edit: 建议使用 EM_UNDO 而不是 WM_UNDO。
264    */
265    pub fn undo(&self) -> bool {
266        let (_, res) = self.send_message(WM_UNDO, WPARAM::default(), LPARAM::default());
267        res != 0
268    }
269}
270
271#[cfg(test)]
272mod test_control {
273    use crate::{
274        common::find_window_ex,
275        control::{
276            edit::{Edit, EC_LEFTMARGIN, FR_FINDNEXT, SB_LINEDOWN},
277            WindowControl,
278        },
279    };
280
281    #[test]
282    fn main() {
283        let h_wnd = find_window_ex(None, None, Some("Notepad++"), None);
284        let Some(h_wnd) = find_window_ex(h_wnd, None, Some("Scintilla"), None) else {
285            return;
286        };
287
288        let control = WindowControl::from(h_wnd);
289        dbg!(control.set_redraw(true));
290        assert!(control.set_text("hello你好"));
291        for _ in 0..100 {
292            assert_eq!(control.get_text(), Some(String::from("hello你好")));
293        }
294        dbg!(control.paint());
295        // dbg!(control.close());
296        control.cut();
297        control.copy();
298        control.clear();
299        control.paste();
300        dbg!(control.undo());
301        test_edit(&control);
302        dbg!(control);
303    }
304
305    pub fn test_edit(control: &WindowControl) {
306        dbg!(control.get_line(0, 2));
307        dbg!(control.line_length(0));
308        dbg!(control.line_index(0));
309        control.replace_sel(true, "123");
310        dbg!(control.set_readonly(false));
311        // dbg!(control.get_text_range(0, 4));
312        control.empty_undo_buffer();
313        dbg!(control.can_undo());
314        dbg!(control.get_first_visible_line());
315        dbg!(control.get_line_count());
316        control.set_modify(true);
317        dbg!(control.get_modify());
318        dbg!(control.get_rect());
319        control.set_sel(2, 4);
320        // dbg!(control.set_sel_ex(3, 5));
321        dbg!(control.get_sel());
322        // dbg!(control.get_sel_ex());
323        // dbg!(control.get_sel_text(3));
324        dbg!(control.line_from_char(3));
325        dbg!(control.line_from_char_ex(2));
326        dbg!(control.scroll(SB_LINEDOWN));
327        dbg!(control.line_scroll(2, 1));
328        control.scroll_caret();
329        dbg!(control.can_paste());
330        dbg!(control.char_from_pos(40, 40));
331        dbg!(control.pos_from_char(1));
332        dbg!(control.selection_type());
333        control.hide_selection(false);
334        dbg!(control.find_text(FR_FINDNEXT, "l", 0, -1));
335        dbg!(control.find_text_ex(FR_FINDNEXT, "l", 0, -1));
336        control.set_margins(EC_LEFTMARGIN, 10);
337        dbg!(control.get_margins());
338    }
339}