如何通过自定义MessageFilter的方式利用按键方式操作控件滚动条[附源代码]

很长一段时间内,一直在做一个SCSF(Smart Client Software
Factory)的项目,已经进入UAT阶段。最近,用户提出了一个要求:需要通过按键方式来控制竖直滚动条。具体来讲就是说,如果一个容器内容过多,用户可以通过按键PageUp和PageDown来控制上下的滚动。刚开始,我试图采用注册事件的方式来实现,但是效果不理想,一来是没有一个单一的地方来对所有相关空间进行事件注册操作,二来如果容器被子控件完全遮挡,容器空间的事件将不会正常出发。有个同事提示采用自定义MessageFilter的方式,我觉得可行,于是进行了一番尝试。

一、实现原理简介

对于一个Windows

Form应用来说,所有事件的触发都是采用消息(Message)的方式来实现的。比如,你点击了一个按钮,Windows会为这个操作之生成一个消息,并将这个消息分发(Dispatch)给按钮对象。如果能够在消息被分发给目标对象之前,能够对该消息进行了拦截,那么我们就可以按照我们希望的方式从新生成一个消息,并将其发送给我希望的目标对象,那么就能过随心所欲地控制目标对象的行为了。而自定义MessageFilter为我们提供了一个最好的消息拦截方式。

就拿我们上面给出控制滚动条的场景来说,当前容器由于内容过多而产生竖直滚动条(假设子控件的宽度和容器相同),用户键入PageDown按键试图向下滚动。Windows为本次键盘操作生成一个消息,并分发给目标对象(可能并不是我们需要控制的当前容器对象)。在此期间,我们通过MessageFilter对该消息实施拦截,从新产生一个基于“向下滚动”操作的消息,并分发给我们需要对其进行控制的容器,那么就实现了对于容器空间滚动条进行控制的目的。

二、实例应用场景简介

熟悉SCSF的朋友应该很清楚,SCSF的通过一个称为Shell的Form作为主界面,利用一个称为Workspace的容器最为整个应用的工作平台。应用动态运行过程中,各个Module的界面采用相同的方式添加到该Workspace之中。下图的就是我们将要演示的例子运行时的截图,为了简单起见,我直接通过一个System.Windows.Forms.TabControl作为Workspace。主菜单的两个菜单项分别代表两个模块,点击相应的菜单项后,会把相应的界面添加到Workspace中。在这里,我通过System.Windows.Forms.UserControl的方式定义Customer和Order模块的界面,当Customer和Order菜单被点击之后,会动态地在TabControl中添加相应的TabPage,并把相应的UserControl置于其中。由于整个TabControl的高度时固定的,而TabPage中显示的内容则依赖于具体的逻辑,所以对于内容过多的TabPage,将会有一个竖直滚动条。而我们需要通过按键的方式控制的就是当前TabPage的这个滚动条。

下面是该Form相关的代码,静态属性ActiveTabPage代表当前显示的TabPage。UserInfo和OrderInfo是两个UserControl,代表与具体模块相关的界面呈现。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Windows.Forms;
   4:  
   5: namespace MessageFilterDemos
   6: {
   7:     public partial class MainForm : Form
   8:     {
   9:         public static TabPage ActiveTabPage
  10:         { get;private set; }
  11:  
  12:         private IDictionary<string, UserControl> keyedViews
  13:         { get; set; }
  14:  
  15:         public MainForm()
  16:         {
  17:             InitializeComponent();
  18:             this.keyedViews = new Dictionary<string, UserControl>();
  19:         }
  20:  
  21:         protected override void OnLoad(EventArgs e)
  22:         {
  23:             base.OnLoad(e);
  24:             this.keyedViews.Add("CustomerInfo", new CustomerInfo());
  25:             this.keyedViews.Add("OrderInfo", new OrderInfo());
  26:         }
  27:  
  28:         private void Show(string key, string text, UserControl view)
  29:         {
  30:             if (!this.mainWorkspace.TabPages.ContainsKey(key))
  31:             {
  32:                 this.mainWorkspace.TabPages.Add(key, text);
  33:                 this.mainWorkspace.TabPages[key].Controls.Add(view);
  34:                 this.mainWorkspace.TabPages[key].AutoScroll = true;
  35:             }
  36:             this.mainWorkspace.SelectedTab = this.mainWorkspace.TabPages[key];
  37:             ActiveTabPage = this.mainWorkspace.TabPages[key];
  38:         }  
  39:  
  40:         private void ordeToolStripMenuItem_Click(object sender, EventArgs e)
  41:         {
  42:             this.Show("OrderInfo", "Order", keyedViews["OrderInfo"]);
  43:         }
  44:  
  45:         private void customerToolStripMenuItem_Click(object sender, EventArgs e)
  46:         {
  47:             this.Show("CustomerInfo", "Customer", keyedViews["CustomerInfo"]);
  48:         }
  49:  
  50:         private void mainWorkspace_SelectedIndexChanged(object sender, EventArgs e)
  51:         {
  52:             ActiveTabPage = this.mainWorkspace.SelectedTab;
  53:         }     
  54:     }
  55: }

 

三、自定义MessageFilter

现在我们进入重点话题,如何创建我们需要的自定义MessageFilter,由于我们这个MessageFilter旨在控制TabPag的滚动条,我们将其命名为ScrollbarControllerMessageFilter。ScrollbarControllerMessageFilter实现了接口System.Windows.Forms.IMessageFilter。下面是IMessageFilter的定义,它仅仅包含一个唯一的成员:PreFilterMessage,对消息的拦截、筛选操作就实现在这里。而Bool类新的返回值表示是否继续将消息分发的目标对象。

   1: public interface IMessageFilter
   2: {
   3:     [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
   4:     bool PreFilterMessage(ref Message m);
   5: }

下面是ScrollbarControllerMessageFilter的定义,代码不是很复杂,在这里只需简单的介绍一下流程:在PreFilterMessage方法中,先判断当前的TabPage是否存在,如果不存在,则不加干涉;然后通过System.Windows.Forms.Message的Msg属性确定当前事件是否是KeyDown,如果不是则直接返回;最后根据System.Windows.Forms.Message的WParam属性判断当前的按键是否是PageUp或者PageDown,并相应的向目标对象(当前的TabPage)发送一个关于向上或者向下滚动的消息。消息的发送通过调用Native方法SendMessage实现。

   1: using System;
   2: using System.Runtime.InteropServices;
   3: using System.Windows.Forms;
   4:  
   5: namespace MessageFilterDemos
   6: {
   7:     public class ScrollbarControllerMessageFilter: IMessageFilter
   8:     {
   9:  
  10:         private const int WM_KEYDOWN = 0x100;//Key down
  11:         private const int WM_VSCROLL = 277; //Scroll
  12:         private const int SB_PAGEUP = 2; // Scroll Up
  13:         private const int SB_PAGEDOWN = 3; //Scroll Down
  14:  
  15:         #region IMessageFilter Members
  16:  
  17:         [DllImport("user32.dll")]
  18:         static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
  19:  
  20:         public bool PreFilterMessage(ref Message m)
  21:         {
  22:             if (MainForm.ActiveTabPage == null)
  23:             {
  24:                 return false;                
  25:             }
  26:  
  27:             if (WM_KEYDOWN != m.Msg)
  28:             {
  29:                 return false;
  30:             }
  31:  
  32:             if (m.WParam.ToInt32() == (int)(Keys.PageUp))
  33:             {
  34:                 SendMessage(MainForm.ActiveTabPage.Handle, WM_VSCROLL, SB_PAGEUP, 0);
  35:                 return true;
  36:             }
  37:  
  38:             if (m.WParam.ToInt32() == (int)(Keys.PageDown))
  39:             {
  40:                 SendMessage(MainForm.ActiveTabPage.Handle, WM_VSCROLL, SB_PAGEDOWN, 0);
  41:                 return true;
  42:             }
  43:  
  44:             return false;
  45:         }
  46:  
  47:         #endregion
  48:     }
  49: }

四、注册ScrollbarControllerMessageFilter

对MessageFilter的注册很简单,仅仅需要的是调用System.Windows.Forms.Application的AddMessageFilter方法即可。实例代码下载地址:http://files.cnblogs.com/artech/MessageFilterDemos.zip

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace MessageFilterDemos
   5: {
   6:     static class Program
   7:     {       
   8:         [STAThread]
   9:         static void Main()
  10:         {
  11:             Application.AddMessageFilter(new ScrollbarControllerMessageFilter());
  12:             Application.EnableVisualStyles();
  13:             Application.SetCompatibleTextRenderingDefault(false);
  14:             Application.Run(new MainForm());
  15:         }
  16:     }
  17: }

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-10-31 11:27:04

如何通过自定义MessageFilter的方式利用按键方式操作控件滚动条[附源代码]的相关文章

利用Aspose.Word控件实现Word文档的操作

原文:利用Aspose.Word控件实现Word文档的操作 Aspose系列的控件,功能都挺好,之前一直在我的Winform开发框架中用Aspose.Cell来做报表输出,可以实现多样化的报表设计及输出,由于一般输出的内容比较正规化或者多数是表格居多,所以一般使用Aspose.Cell来实现我想要的各种Excel报表输出.虽然一直也知道Aspose.Word是用来生成Word文档的,而且深信其也是一个很强大的控件,但一直没用用到,所以就不是很熟悉. 偶然一次机会,一个项目的报表功能指定需要导出为

asp.net中使用自定义控件的方式实现一个分页控件的代码_实用技巧

一.概述 在web开发中,常常需要显示一些数据,而为了方便排版及浏览,我们只需要显示所有记录中的一部分.一般情况下,我们采用分页来实现这个需求.实现分页的方法多种多样,在本文中,我们采用了一个分页空间来记录记录总数.当前页.总页数及页面大小等.为了有一个直观上的印象,先展示该控件运行后的效果,效果如下图所示: 二.实现方案 为了实现该效果图,在asp.net中,可以使用Custom Controls and User Controls两种方式,User Controls的实现方式及其简单,而且使

利用Aspose.Word控件和Aspose.Cell控件,实现Word文档和Excel文档的模板化导出

我们知道,一般都导出的Word文档或者Excel文档,基本上分为两类,一类是动态生成全部文档的内容方式,一种是基于固定模板化的内容输出,后者在很多场合用的比较多,这也是企业报表规范化的一个体现. 我的博客介绍过几篇关于Aspose.Word控件和Aspose.Cell控件的使用操作,如下所示. <使用Aspose.Cell控件实现Excel高难度报表的生成(一)> <使用Aspose.Cell控件实现Excel高难度报表的生成(二)> <使用Aspose.Cell控件实现Ex

利用Aspose.Cell控件导入Excel非强类型的数据

导入Excel的操作是非常常见的操作,可以使用Aspose.Cell.APOI.MyXls.OLEDB.Excel VBA等操作Excel文件,从而实现数据的导入,在导入数据的时候,如果是强类型的数据,那么这几种方式好像都表现差不多,正常操作能够导入数据.如果是非强类型的数据,那么就需要特别注意了,一般情况下,导入的DataTable数据列的类型是以第一行内容作为确定列类型的,本文介绍利用Aspose.Cell控件导入Excel非强类型的数据的操作. 什么是强类型的数据呢,就是在Excel表格中

控件滚动条的位置可以自定义么?比如word软件的滚动条的位置就不是定格的

问题描述 控件滚动条的位置可以自定义么?比如word软件的滚动条的位置就不是定格的 控件滚动条的位置可以自定义么?比如word软件的滚动条的位置就不是定格的,可以添加工具栏按钮在滚动条左边,这个是用什么属性设置的? 解决方案 滚动条位置

js调试-利用easyui panel控件中的href 引入一个html代码片段,片段里面的js怎么调试?

问题描述 利用easyui panel控件中的href 引入一个html代码片段,片段里面的js怎么调试? Firefox和chrome的js调试页面都不显示片段js文件.唯一的办法是在要调试的地方加上debugger: 关键字.可是这样很麻烦,有没有其他的办法供调试用呢?

Android 自定义View 三板斧之二——组合现有控件

通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 上文说过了如何继承现有控件来自定义控件,这节我们来讨论第二个议题.怎么将控件组合来实现一个功能强大的自定义控件. 先看看创建组合控件的好处吧,创建组合控件能够很好的创建具有组合功能的控件集合.那我们一般又是怎么做的了,一般我们来继承一个合适的ViewGroup,再为他创建一个新功能,从而就形成了一个新功能的控件.我们还

利用Dojo EnhancedGrid控件进行数据异步传输和保存

客户需要做一个公司http://www.aliyun.com/zixun/aggregation/13617.html">信息管理界面,管理员进入后只要点击查询按钮就能查出所需要的信息,在查询的过程中页面不需要刷新,减少用户的等待时间. 前台框架设计:struts 2.0+Dojo 1.7.1 业务逻辑:Spring 3.1 持久层:mybatis 3.0.6 + DB2 9.7 展现层:介绍示例应用程序的展现层的设计 Dojo EnhancedGrid 简介 顾名思义,EnhancedG

利用微软网格控件进行编辑输入

Visual Basic中提供了许多标准控件和定制控件,每一个控件都可以提供一组特殊的用户界面和编程能力.充分利用每一个控件的特性和方法,可以使编程工作更加容易.简单. 微软网格控件MSFlexGrid是个定制控件.使用微软网格控件,可以按行列顺序显示正文.数字和图片,就象电子表格一样.网格的高度.宽度和其它特性都可以进行调整,而且网格的行和列可以单个地或成组地进行操作.MSFlexGrid控件对包含在内的单元内容可进行分类.合并以及格式化,并且可以与数据库控件相绑定.MSFlexGrid控件具