none
关于两个窗口内的控件互操作的通用方法有么 RRS feed

  • 问题

  • 比如说移动窗体1内的panel,窗体2内的gridview相应坐标数据改变,我一般把整个gridview作为参数传给panel,是不是不是好的做法啊,通用的比较好的方法是什么,用代理么?
    2009年5月3日 2:55

答案

  • 比如说移动窗体1内的panel,窗体2内的gridview相应坐标数据改变,我一般把整个gridview作为参数传给panel,是不是不是好的做法啊,通用的比较好的方法是什么,用代理么?
    可以是用代理(delegete & event)。

    移动窗体1会暴露出一个事件(LocationChangedEvent),当窗体移动的时候,该事件会被引发,同时传递LocationnChangedEventArgs一个参数,里面包含了处理该事件需要的信息,比如旧的位置,以及新的位置。

    窗体2里会订阅(subsribe)这个事件,当收取该事件的一个实例的时候,更新GridView。

    这样设计的一个好处是,引发事件的对象,不必需要知道订阅事件的对象的存在。这样意味着窗体1和窗体2完全可以分配给2个不同的开发者去开发。
    而且,这样还可以提高系统的可扩展性。比如,若是需要有窗体3,来处理窗体1的移动的事件,那么窗体1的代码可以完全不用改动;事实上,只是需要写一个新的窗体3即可。
    最后,这里不用把整个窗体1的引用暴露给窗体2,可以减少bug的出现可能性。若是把整个窗体1暴露给窗体2,那么后者的开发者就可能对窗体1做任何的事情--包括一些破坏,无论是有意的,还是无意的。


    Visual Studio就是遵循如此设计思想的。

    设想下,当从处理方案(Solution)里面删除一个源文件的时候,会有多少事情要处理?若是文件没有保存,要提示用户保存;处理方案的TreeView要更新;若是在Editor里面该文件被打开,需要关闭Editor;ToolBar上面的按钮需要更新;ClassView里面的类列表要更新;等等等。若是如此的写代码:

    --- TreeView控件内部:
    void DeleteFile() {
        if (AskUserConfirm()) }
              CloseEditor();
              UpdateToolBar();
              UpdateClassViewTree();
              UpdateSolutionFileTreeView();
              MoveFileToTrash();
        }
    }
    这样写代码有至少2个缺点:
    1。扩展性弱。若是添加了新的控件,TreeView控件要修改代码并重新编译;
    2。没有很好的遵循包装的思想(Encapsulation)。比如上述代码中的,CloseEditor(),就需要把Editor控件的一个引用放在TreeView控件内部,这样的暴露有些过多;软件设计的一个思想是,除了该需要暴露的,其余的都不应该暴露给另外一方。所以,把一个控件的引用完全暴露给另外一个控件是不合适的。


    若是用代理的思路来设计,可以如此:

    TreeView会有一个事件,名字叫做UserDeleteFile,该事件会传递一个参数,里面包含了用户欲删除的文件名字。
    Editor, ToolBar, ClassViewTree, 这些控件会订阅该事件,同时进行相应的处理:

    --- Editor 的 OnLoad
    void OnLoad() {
        Program.TreeViews["Solution"].UserDeleteFile += 
           new UserDeleteFileEventHandler(this.OnUserDeleteFile));
    }
    --- Editor 的 OnUserDeleteFile
    void OnUserDeleteFile(Object sender, UserDeleteFileEventArgs e) {
         if (ConfirmDeletion()) {
              Program.Editors.Current.Close();
         } else {
              e.Cancel = true;
         }
    }
    


    MCPD (Windows & Web)
    2009年5月3日 7:25
  • 也不能说你说得不够详细

    比如说 您要写一个扫雷   要相应   64*64个按钮的事件   然后在另一个窗体即时显示用户点button的log(listbox)  这样就可以把64*64个操作总结为一个模式

    或者设计模式中的 观察者模式   其实也是一种可以应用于跨窗口程序的模式    但是他更加专注于多个视图和一个数据源之间的协同

    您的操作也可以设计自己的模式  模式的产生很微妙   往往要看当事人  我们在旁边往往会误会您的意图  起到反效果

    所以我想我们只要能够做到对您有所启发  就很开心了


    工作突然有点忙 嘿嘿
    2009年5月4日 3:06
    版主

全部回复

  • 你好!
         可以自定义的公共属性来暴露你想传递的参数啊!
    周雪峰
    2009年5月3日 3:56
    版主
  • 你好!
         可以自定义的公共属性来暴露你想传递的参数啊!
    周雪峰

     用属性和参数原理都是一样的吧?

    如果有n个控件需要互操作,岂不要每个控件要暴n-1个属性,不是个好方法吧
    2009年5月3日 4:05
  • 既然你要操作那么多控件,为什么不把这个控件写成 usercontrol呢
    知识改变命运,奋斗成就人生!
    2009年5月3日 5:47
    版主
  • 既然你要操作那么多控件,为什么不把这个控件写成 usercontrol呢
    知识改变命运,奋斗成就人生!

    不考虑别的,就考虑该问题
    2009年5月3日 5:49
  • 把把定义控件的 private 改为 public 通过 Form.控件来访问
    知识改变命运,奋斗成就人生!
    2009年5月3日 5:53
    版主
  • 也是一个办法,看过很多代理的概念,但几乎没用到多,各位在实际编程中一般用在哪个地方呢?
    2009年5月3日 6:16
  • 代理是指什么?
    知识改变命运,奋斗成就人生!
    2009年5月3日 6:26
    版主
  • 比如说移动窗体1内的panel,窗体2内的gridview相应坐标数据改变,我一般把整个gridview作为参数传给panel,是不是不是好的做法啊,通用的比较好的方法是什么,用代理么?
    可以是用代理(delegete & event)。

    移动窗体1会暴露出一个事件(LocationChangedEvent),当窗体移动的时候,该事件会被引发,同时传递LocationnChangedEventArgs一个参数,里面包含了处理该事件需要的信息,比如旧的位置,以及新的位置。

    窗体2里会订阅(subsribe)这个事件,当收取该事件的一个实例的时候,更新GridView。

    这样设计的一个好处是,引发事件的对象,不必需要知道订阅事件的对象的存在。这样意味着窗体1和窗体2完全可以分配给2个不同的开发者去开发。
    而且,这样还可以提高系统的可扩展性。比如,若是需要有窗体3,来处理窗体1的移动的事件,那么窗体1的代码可以完全不用改动;事实上,只是需要写一个新的窗体3即可。
    最后,这里不用把整个窗体1的引用暴露给窗体2,可以减少bug的出现可能性。若是把整个窗体1暴露给窗体2,那么后者的开发者就可能对窗体1做任何的事情--包括一些破坏,无论是有意的,还是无意的。


    Visual Studio就是遵循如此设计思想的。

    设想下,当从处理方案(Solution)里面删除一个源文件的时候,会有多少事情要处理?若是文件没有保存,要提示用户保存;处理方案的TreeView要更新;若是在Editor里面该文件被打开,需要关闭Editor;ToolBar上面的按钮需要更新;ClassView里面的类列表要更新;等等等。若是如此的写代码:

    --- TreeView控件内部:
    void DeleteFile() {
        if (AskUserConfirm()) }
              CloseEditor();
              UpdateToolBar();
              UpdateClassViewTree();
              UpdateSolutionFileTreeView();
              MoveFileToTrash();
        }
    }
    这样写代码有至少2个缺点:
    1。扩展性弱。若是添加了新的控件,TreeView控件要修改代码并重新编译;
    2。没有很好的遵循包装的思想(Encapsulation)。比如上述代码中的,CloseEditor(),就需要把Editor控件的一个引用放在TreeView控件内部,这样的暴露有些过多;软件设计的一个思想是,除了该需要暴露的,其余的都不应该暴露给另外一方。所以,把一个控件的引用完全暴露给另外一个控件是不合适的。


    若是用代理的思路来设计,可以如此:

    TreeView会有一个事件,名字叫做UserDeleteFile,该事件会传递一个参数,里面包含了用户欲删除的文件名字。
    Editor, ToolBar, ClassViewTree, 这些控件会订阅该事件,同时进行相应的处理:

    --- Editor 的 OnLoad
    void OnLoad() {
        Program.TreeViews["Solution"].UserDeleteFile += 
           new UserDeleteFileEventHandler(this.OnUserDeleteFile));
    }
    --- Editor 的 OnUserDeleteFile
    void OnUserDeleteFile(Object sender, UserDeleteFileEventArgs e) {
         if (ConfirmDeletion()) {
              Program.Editors.Current.Close();
         } else {
              e.Cancel = true;
         }
    }
    


    MCPD (Windows & Web)
    2009年5月3日 7:25
  • TreeView会有一个事件,名字叫做UserDeleteFile

    够专业,关于事件的定义是通过代理吧,这块概念我总是搞不清楚,可否介绍下
    2009年5月3日 12:01
  • 不好意思,我一直没有理解你说的代理是指什么?是delegate吗?
    请具体说明可以吗?
    周雪峰
    2009年5月3日 12:50
    版主
  • 不好意思,我一直没有理解你说的代理是指什么?是delegate吗?
    请具体说明可以吗?
    周雪峰

    对,也有翻译成委托的,我没说清楚
    2009年5月3日 12:56
  • TreeView会有一个事件,名字叫做UserDeleteFile

    够专业,关于事件的定义是通过代理吧,这块概念我总是搞不清楚,可否介绍下
    代理 (delegate) 可以简单的理解成 函数指针(function pointer)。若是你对指针有了解的话,那么解释代理就容易了。

    比如,如下代码:

    static void Foo(String msg) { Console.WriteLine(msg); }
    delegate void FooCallback(String msg);
    
    static void Main() {
          FooCallback firstDelegate = new FooCallback(Foo);
          FooCallback secondDelegate = Foo;  // 同上一行是等同的。
    
          firstDelegate.Invoke("first delegate");
          secondDelegate.Invoke("second delegate");
    }

    打印的结果是
    first delegate
    second delegate

    对于上面的例子,你可以看到,在 Main() 里面为了调用函数 Foo(msg) 大费周章。其实可以完全直接来一个 Foo("first delegate"); Foo("second delegate"); 这样了事。

    对于代理 (delegate),真正的用途则是让第三方对象来调用,尤其是在不方便把本身的引用直接暴露的情况下。对于WinForm里面的事件处理,就是一个极好的例子。

    事件 (event),是代理的集合。也就是,一个事件就是一些函数指针的集合。

    首先,我们需要定义一个代理:

    public delegate void FooEventHandler(Object sender, FooEventArgs e);  
    // 注:把跟事件相关的代理命名为 XXXEventHandler是一个习惯。函数本身的参数 (Object sender, FooEventArgs e) 也不是强迫的,但是推荐如此。

    然后,在你的一个类(事件引发者)中,声明一个事件:

    public class EventRaiser {
         public static event FooEventHandler Foo;
    }

    // 这里为了方便我就简单定义成 static。非 static 的事件也可以的。

    然后,在第三方类当中,订阅该事件:

    public class SubscriberOne {
        public SubscriberOne() {
            EventRaiser.Foo += this.OnFoo;
        }

        private void OnFoo(Object sender, FooEventArgs e) {
             Console.WriteLine("Say, one.");
        }
    }

    public class SubscriberTwo {
        public SubscriberTwo() {
            EventRaiser.Foo += this.OnFoo;
        }

        private void OnFoo(Object sender, FooEventArgs e) {
             Console.WriteLine("Say, two.");
        }
    }

    这里,2个类 SubscriberOne 和 SubscriberTwo 都在构造函数里面 订阅了事件 Foo。这里你注意到了,用的是 +=。若是联想到刚才我说的,event其实是代理的集合,那么就很好理解了。

    注意,若是在SubscriberTwo里面,代码改成了 EventRaiser.Foo = this.OnFoo; 那么集合里面所有的其他代理都会被覆盖掉。操作之后,事件里面唯一存在的代理就是SubscriberTwo里面的OnFoo函数了。。

    那么如何引发事件呢?引发事件必须在事件引发者里面操作。回到刚才的 EventRaiser 类里面:

    public class EventRaiser {
        private static void RaiseFoo(FooEventArgs e) {
             if (Foo != null)  {
                  Foo(null, e);
             }
        }
    }

    这样,一旦 EventRaiser.RaiseFoo 这个函数被执行,那么在事件 Foo 里面所有的代理都会被执行一遍,按照先进先执行的原则。注意,这里检查了Foo是否为空。因为,若是这个事件没有被订阅的话,那么它就是null。

    你可以简单写一些代码来熟悉这些概念。


    其实代理和事件达到的效果,和 观察者模式( Observer Design Pattern ) 的目的是一样的。若是你对设计模式 (Design Pattern)有兴趣的话,可以看看 http://zh.wikipedia.org/wiki/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F

    在Java里面,因为没有代理的存在,所以观察者模式很流行的。当然在C#里面写观察者模式的代码也是可以的,不过没有代理和事件方便罢了。





    MCPD (Windows & Web)
    2009年5月3日 13:19
  • 原来是这个意思啊!
    事件实际上也是使用委托机制实现的,每个事件都是一个委托实例,都是EventHandler委托类(或其派生类)的实例!
    周雪峰
    2009年5月3日 13:26
    版主
  • 这种东西没有必要自己写委托拉  事件处理就好了

    你所说的“统一的通用的好的”  方法    其实是你觉得这种操作应该高度抽象  并且具有一定的模式

    很遗憾的  我们没有看到你的设计需求   所以没有办法根据你的设计需求中若干重复的部分设计一套抽象模式阿 
    工作突然有点忙 嘿嘿
    2009年5月4日 0:06
    版主
  • 这种东西没有必要自己写委托拉  事件处理就好了

    你所说的“统一的通用的好的”  方法    其实是你觉得这种操作应该高度抽象  并且具有一定的模式

    很遗憾的  我们没有看到你的设计需求   所以没有办法根据你的设计需求中若干重复的部分设计一套抽象模式阿 
    工作突然有点忙 嘿嘿

    你是说我说的不够详细么?
    2009年5月4日 0:37
  • 也不能说你说得不够详细

    比如说 您要写一个扫雷   要相应   64*64个按钮的事件   然后在另一个窗体即时显示用户点button的log(listbox)  这样就可以把64*64个操作总结为一个模式

    或者设计模式中的 观察者模式   其实也是一种可以应用于跨窗口程序的模式    但是他更加专注于多个视图和一个数据源之间的协同

    您的操作也可以设计自己的模式  模式的产生很微妙   往往要看当事人  我们在旁边往往会误会您的意图  起到反效果

    所以我想我们只要能够做到对您有所启发  就很开心了


    工作突然有点忙 嘿嘿
    2009年5月4日 3:06
    版主