• 如何在。net中扩展本机消息框对话框


    介绍 在本文中,我将解释如何在托管代码中扩展本机消息框。有两个函数,我将添加到消息框: 显示倒计时消息,并在指定超时过期时自动关闭消息框,并返回对话框的默认选项。在消息框中添加一个复选框控件,以给用户一个额外的选项。 背景 有些文章已经通过Windows挂钩实现了这些函数。虽然它们非常有效,但我认为这种技术太复杂,难以掌握,特别是对于初学者。所以,我决定尝试另一种方法。Thammadi的文章给了我一个更好的主意。他使用Windows计时器服务以编程方式关闭消息框;我也可以做同样的事来实现我的目标。 声明本地对象 由于消息框窗口是一个本机窗口,我们只能通过Windows api访问它。下面是我们完成此任务所需的api列表。其中大部分都是使用Lutz Roeder's Reflector从System.Window.Forms.dll中分解出来的。:) 隐藏,收缩,复制Code

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, 
           int msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool SetWindowText(IntPtr hWnd, string text);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int GetWindowText(IntPtr hWnd, 
           StringBuilder text, int maxCount);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static IntPtr FindWindow(string className, string caption);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static IntPtr FindWindowEx(IntPtr hwndParent, 
           IntPtr hwndChildAfter, string className, string caption);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static int GetWindowLong(IntPtr hWnd, int index);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static IntPtr SetWindowLong(IntPtr hWnd, 
           int index, IntPtr newLong);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static IntPtr SetParent(IntPtr hWndChild, 
           IntPtr hWndNewParent);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool EnumChildWindows(IntPtr hWndParent, 
           EnumChildProc callback, IntPtr param);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int GetClassName(IntPtr hWnd, 
           StringBuilder className, int maxCount);
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    public static extern bool GetWindowRect(IntPtr hWnd, 
           [In, Out] ref NativeMethods.RECT rect);
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    public static extern bool GetClientRect(IntPtr hWnd, 
           [In, Out] ref NativeMethods.RECT rect);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, 
           int x, int y, int cx, int cy, int flags);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool ScreenToClient(IntPtr hWnd, 
           [In, Out] ref NativeMethods.POINT point);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool DestroyWindow(IntPtr hWnd);

    我们还需要两种结构类型来处理本机窗口,见下面的代码: 隐藏,复制Code

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }

    我们还需要一些常数。详细信息请参考源代码中的nativememethods .cs文件。 处理消息框 托管MessageBox类调用本机API消息框来显示消息框。如果我们想控制它,并避免使用Windows挂钩,我们必须在消息框显示之后做一些事情。众所周知,消息框是一个模态对话框,我们不能仅仅在MessageBox.Show()语句之后编写代码来处理对话框。我们需要在程序被模态对话框阻塞时执行代码。计时器组件可以帮助我们做到这一点。 设置计时器组件 可以在“工具箱”的“组件”部分中找到计时器组件,然后将其拖放到窗体中。该组件有四个重要成员:间隔属性、启动和停止方法以及滴答事件。我们需要做的是为Tick事件创建一个事件处理方法。默认情况下,该方法的名称为timer1_Tick,所有处理代码都在那里。 准备显示消息框 只需一条语句就可以显示消息框。但是为了处理它,我们必须首先启动计时器并进行一些初始化工作。由于我们将在第一次调用timer1_Tick方法时进行一些奇妙的工作,所以我们应该将timer1的Interval属性设置为一个非常小的值,这样用户就不会注意到对消息框所做的更改。 隐藏,复制Code

    // initializations
    
    seconds = -1;
    timer1.Interval = 10;
    // starts the timer
    
    timer1.Start();
    // shows the message box with Yes, No, Cancel bttons,
    // and the default button is No.
    
    System.Windows.Forms.MessageBox.Show(this, 
        "Message body goes here.", 
        "Dialog Caption",
        MessageBoxButtons.YesNoCancel,
        MessageBoxIcon.Information,
        MessageBoxDefaultButton.Button2);

    超时函数的实现 超时功能使消息框在指定的秒数之后自动关闭。因此,我们需要一个字段变量来保存经过的秒,并将该变量命名为seconds。因为我们想要向用户显示倒计时消息,所以还需要一个Label控件。 实现timer1_Tick方法 首先,在这个方法中,我们需要确保消息框对话框已经实际打开。调用FindWindow函数来获得这个对话框的句柄。如上所示,这个API有两个参数。第一个参数指定我们想要找到的窗口的类名,我们可以传递null来忽略它。第二个是窗口标题栏中显示的文本。如果返回值非零,则表示已经找到消息框,返回值是消息框的句柄。 在找到消息框之后,我们应该确定这是否是第一次调用方法(seconds == -1)。如果是这样,我们需要停止计时器,装饰消息框,将timer1的间隔更改为1000,并重新启动它。每次调用该方法时,变量秒数都应该增加。然后,检查变量是否超过超时。当检测到过期时,立即关闭消息框。当计时器滴答作响时,我们也可以显示一个倒计时消息来通知用户。为此,我们应该在消息框的第一个刻度处放置一个Label控件。有关详细信息,请参阅以下两个部分。 如果未找到该消息框,则应认为该消息框已被关闭。所以,停止计时器。 下面的代码演示了timer1_Tick方法的一个简单实现: 隐藏,收缩,复制Code

    IntPtr hWndMsgBox;
    
    // looks for our message box
    
    hWndMsgBox = FindWindow(null, "Dialog Caption");
    if (hWndMsgbox != IntPtr.Zero)
    {
        if (seconds == -1)
        {
            timer1.Stop();
            
            // todo: adds Label control to the message box at the first tick
            // ...
    
            timer1.Interval = 1000;
            timer1.Start();
        }
        
        // updates the countdown message.
        // suppose the timeout value is 30 seconds.
    
        label.Text = string.Format("{0} seconds elapsed.", 30 - ++seconds);
        
        if (seconds >= 30)
        {
            // timeout expired
            // todo: closes the message box
            // ...
            // stops the timer
    
            timer1.Stop();
        }
    }
    else
        timer1.Stop();

    向消息框添加Label控件 可以通过调用API函数SetParent向本机窗口添加控件。我们应该关心的是在哪里以及如何放置这个控件。在我的项目中,我将Label控件放置在消息框的左下角。当然,仅仅放置控件是不够的,我们还应该修改消息框的尺寸以适应标签。 这里,我们需要以下API函数: GetWindowRect——受潮湿腐烂rieves消息框边界矩形的尺寸。SetWindowPos——更改消息框的大小。GetClientRect—检索消息框的客户区域的坐标。 添加Label控件的代码示例(请参见消息框)。详细说明DecorateMessageBox方法): 隐藏,复制Code

    RECT rect = new RECT();
    
    // retrives current dimensions of the message box
    
    GetWindowRect(hWndMsgBox, ref rect);
    // increase the message box's height to fit label
    
    SetWindowPos(hWndMsgBox, IntPtr.Zero, 0, 0, rect.right - rect.left, 
                 rect.bottom - rect.top + lable.Height, SWP_NOZORDER | SWP_NOMOVE);
    
    // adds label to the message box
    
    SetParent(lable.Handle, hWndMsg);
    // retrieves the size of the message box's client area
    
    GetClientRect(hWndMsgBox, ref rect);
    // sets labels location
    
    label.Location = new Point(0, rect.bottom - label.Height);

    以编程方式关闭消息框 要以编程方式关闭消息框,需要考虑一件重要的事情。如果我们只是简单地发送一个WM_CLOSE消息或调用DestoryWindow函数,返回的值将不是默认按钮的相应值。我所做的是发送一个WM_COMMAND消息来模拟按钮点击。 WM_COMMAND消息需要三个参数,一个通知代码,一个标识符,以及被单击按钮的句柄。可以直接从MSDN检索通知代码,但是其他参数应该通过编程方式检索。考虑到重用这些参数,我创建了另外两个分类来保存消息框的所有子控件的信息,MessageBoxChild和MessageBoxChildCollection。 要获得所有子控件的信息,我们应该调用EnumChildWindows函数。此函数枚举消息框的所有子控件,并调用应用程序定义的回调函数。在托管代码中,回调函数可以与委托交替使用。委托声明如下: 隐藏,复制Code

    public delegate bool EnumChildProc(IntPtr hWnd, IntPtr param);

    在回调函数中,我们检索每个子元素的句柄、标识符、类名、样式等。这些信息存储在MessageBoxChild类中,并将逐个添加到MessageBoxChildCollection类的实例中。在向集合对象添加时,它确定控件是什么。因此,我们可以方便地使用他们以后。 枚举消息框的子控件的代码示例如下: 隐藏,复制Code

    // clears the collection object
    
    collection.Clear();
    // enumerates all child controls of the message box
    
    EnumChildWindows(hWndMsgBox, new EnumChildProc(EnumChildren), IntPtr.Zero);

    这里显示了EnumChildren方法的代码示例(请参见消息框)。EnumChildren方法): 隐藏,收缩,复制Code

    private bool EnumChildren(IntPtr hWnd, IntPtr lParam)
    {
        // local variable declearation
    
        StringBuilder name = new StringBuilder(1024);
        StringBuilder caption = new StringBuilder(1024);
        RECT rect = new RECT();
        int style, id;
    
        // retrieves control's class name.
    
        GetClassName(hWnd, name, 1024);
        // retrieves control's caption.
    
        GetWindowText(hWnd, caption, 1024);
        // retrieves control's bounds.
    
        GetWindowRect(hWnd, ref rect);
    
        // retrieves control's style.
    
        style = GetWindowLong(hWnd, GWL_STYLE);
        // retrieves control's identifier.
    
        id = GetWindowLong(hWnd, GWL_ID);
    
        // creates a new instance of MessageBoxChild
        // and add it to the collection
    
        collection.Add(new MessageBoxChild(hWnd, name.ToString(), 
                       caption.ToString(), rect, style, id));
    
        // returns true for continue enumerating
    
        return true;
    }

    这里显示了用于确定控件是什么的代码示例(参见MessageBoxChildCollection)。添加方法): 隐藏,复制Code

    if (child.ClassName == "Button")
        this.buttons.Add(child);
    else if (child.ClassName == "Static")
    {
        if ((child.Style & SS_ICON) != 0)
            this.icon = child;
        else
            this.label = child;
    }

    一旦我们需要的所有信息都准备好了,我们就可以从集合对象中获取默认按钮的信息。然后,我们调用SendMessage函数来发送WM_COMMAND消息来模拟默认按钮的单击。 隐藏,复制Code

    // retrives information of the default button control
    
    MessageBoxChild bn = collection.GetButton(MessageBoxDefaultButton.Button2);
    // send message to the message box the simulate button clicking
    
    SendMessage(hWndMsgBox, WM_COMMAND,
        (BN_CLICKED << 16) | bn.Id,
        bn.Handle);

    实现一个复选框函数 通过向消息框中添加一个ChecBox控件,我们可以给用户一个额外的选项来进行选择。通常,这个选项是“不要再显示这个”。此函数的实现类似于添加Label控件。不同之处在于位置。我将复选框控件放置在按钮上方,并将其左边缘与对话框的消息对齐。因此,我们不仅应该增加消息框的高度,还应该将按钮降低。 关于如何检索子控件的信息,我已经在前一节中解释过了。但是,我们之前检索到的坐标是相对于屏幕左上角的。因此,我们必须将它们转换为相对于消息框窗口客户区域左上角的坐标。 添加复选框控件的代码示例如下所示(请参见MessageBox)。详细说明DecorateMessageBox方法): 隐藏,收缩,复制Code

    POINT point = new POINT();
    RECT rect = new RECT();
    int top;
    // move the buttons lower
    
    foreach (MessageBoxChild bn in collection.Buttons)
    {
        point.x = bn.Rectangle.left;
        point.y = bn.Rectangle.top;
        
        // converts screen relative coordinates to client relative corrdinates
        ScreenToClient(hWndMsgBox, ref point);
        
        // moves button
        SetWindowPos(bn.Handle, IntPtr.Zero,
            point.x, point.y
            + 10 /* vertical space between the checkbox and the buttons */
            + checkBox.Height,
            0, 0,
            SWP_NOZORDER | SWP_NOSIZE);
    }
    
    // saves Y-coordinate for the CheckBox control
    top = point.y;
    
    // retrieves and converts the coordinates of the native label control
    point.x = collection.Label.Rectangle.left;
    point.y = collection.Label.Rectangle.top;
    ScreenToClient(hWndMsgBox, point);
    
    // adds the CheckBox control
    
    SetParent(checkBox.Handle, hWndMsgBox);
    // sets the CheckBox control's location
    
    checkBox.Location = new Point(point.x, top);
    
    // retrieves current dimensions of the message box
    
    GetWindowRect(hWndMsgBox, ref rect);
    // resizes the message box
    
    SetWindowPos(hWndMsgBox, IntPtr.Zero, 0, 0,
        rect.right - rect.left, rect.bottom - rect.top + checkBox.Height + 10,
        SWP_NOZORDER | SWP_NOMOVE);

    创建自己的组件 我已经介绍了本文的所有关键知识。接下来,我们应该将它们组合起来并封装到单个组件中。在该组件中,我们需要建立串行属性和方法,并提供设计时支持,以使用户能够更好地使用该组件。我所建立的成员见下图: 对于设计时的支持,我们只需要在属性中添加一些属性,如DescriptionAttribute、CategoryAttribute、DefaultValueAttribute等。 隐藏,收缩,复制Code

    [DefaultValue(0), Category("Timeout")]
    [Description("Specifies the amount of seconds that the " + 
        "message box appears. Set it to 0 to disable the function.")]
    public int Timeout
    {
        get { return timeout; }
        set
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException("Timeout", 
                          "Timeout cannot be less than 0.");
    
            timeout = value;
        }
    }
    
    [Category("Behavior"), 
     Description("The text to display in the message box.")]
    [Editor("System.ComponentModel.Design.MultilineStringEditor, 
            System.Design, Version=2.0.0.0, Culture=neutral, 
            PublicKeyToken=b03f5f7f11d50a3a",
            typeof(System.Drawing.Design.UITypeEditor))]
    public string Message
    {
        get { return message; }
        set { message = value; }
    }

    请注意上面示例代码中的Message属性。我给它添加了一个EditorAttribute。这允许用户在设计时在属性网格中输入多行文本。 为了显示消息框,需要一个ShowDialog方法。在前面的“准备显示消息框”一节中,我已经解释了如何显示消息框。但是,为了使消息框具有灵活的外观,我们必须使用变量而不是常量值。代码是这样的: 隐藏,复制Code

    System.Windows.Forms.MessageBox.Show(owner, this.Message, this.Caption,
           this.Buttons, this.Icon, this.DefaultButton);

    使用组件 在成功生成包含组件的项目后,工具箱中将显示一个新图标。要使用它,可以将其拖放到窗体中,或者在运行时创建新实例。下面的代码演示了第二个选项: 隐藏,复制Code

    // creates a new instance of this component
    
    MessageBox msgBox = new MessageBox();
    
    // sets the text to display
    
    msgBox.Message = "Hello, world!";
    // sets the icon of the message box
    
    msgBox.Icon = MessageBoxIcon.Information;
    // sets the timeout to 10 seconds
    
    msgBox.Timeout = 10;
    
    // shows the message box
    
    msgBox.ShowDialog(this);

    的兴趣点 我从来没有写过这么大的文章re,这是我第一次,哈哈。因为我的英语水平很差,所以花了很长时间才完成。在写这篇文章的时候,我参考了一些翻译新单词和拼写的软件。所以我认为这是一项艰苦的工作,哈哈,开个玩笑。最后,我希望你能明白我的意思。 历史 一个也没有。 本文转载于:http://www.diyabc.com/frontweb/news3727.html

  • 相关阅读:
    读《高效能人士的七个习惯》有感
    Springboot中的日志
    fastjson JSON.toJavaObject() 实体类首字母大写属性无法解析问题
    java多线程编程实例
    IDEA插件配置推荐
    Spring Boot 自定义数据源 DruidDataSource
    zookeeper环境搭建
    eureka注册中心的使用
    记事本编码
    Chrome浏览器基本操作
  • 原文地址:https://www.cnblogs.com/Dincat/p/13461325.html
Copyright © 2020-2023  润新知