委派與事件

      在〈委派與事件〉中尚無留言

委派

delegate, 委派的意思, 依中文解釋, 是指定某些東西, 派發給別人使用.

在此可以把delegate想成是一個容器, 而裏面裝的, 都是目前類別(A)裏的方法名稱, 然後包裝成一個delegate物件.

當別的類別(B) 在某一時間點, 希望執行A裏面的方法時, 就可以在B裏先接收delegate物件, 然後將delegate解開, 執行裏面的方法.

下面代碼中模擬在主程式中開啟了一個地圖, 但因地圖開啟時間相當費時, 所以在MahalMap建構子中使用新的執行緒執行. 當地圖開啟後, 回到UI,在TextBox填入 “Map is ready”。

首先, 在namespace 宣告一個delegate OnMapReadyListener , 宣告在namespace裏的delegate, 就可以跨不同的類別. 其實此delegate就如同是一個class, 由此new 出一個物件, 並將onMpaReadyListener()方法包進去.

然後再將此物件傳送到MahalMap中. 待MahalMap執行緒完成後, 直接調用此物件.

using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApp1
{
    public delegate void OnMapReadyListener();
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            MahalMap mahalMap=new MahalMap(this, new OnMapReadyListener(onMapReadyListener));
        }
        private void onMapReadyListener()
        {
            txt.Text = "Map is ready";
        }
    }
    class MahalMap
    {
        Window window;
        public MahalMap(Window window, OnMapReadyListener onMapReadyListener)
        {
            this.window = window;
            new Thread(new ThreadStart(
                delegate
                {
                    Thread.Sleep(5000);
                    window.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                    {
                        onMapReadyListener.Invoke();
                    }));
                }
                )).Start();
        }
    }
}

在MahalMap中調用委派的方法時, 使用onMapReadyListener.Invoke(), 也可簡化成 onMapReadyListener();

MahalMap在啟動新的執行緒時, 最原始的方法, 是將要執行的任務寫成一個方法, 再由
new Thread(new ThreadStart(task)).Start()執行, 而此行又可簡化成
new Thread(task).Start(), 因為編譯器會自動加上new ThreadStrart().

然而此方法只被執行一次, 因此可以在ThreadStart()中, 把任務包含在delegate中

原始的寫法如下

    class MahalMap
    {
        Window window;
        OnMapReadyListener onMapReadyListener;
        //public event OnMapReadyListener mapListener;
        public MahalMap(Window window, OnMapReadyListener onMapReadyListener)
        {
            this.window = window;
            this.onMapReadyListener = onMapReadyListener;
            new Thread(new ThreadStart(initMap)).Start();
            //可以簡化成如下
            //new Thread(initMap).Start();
        }
        private void initMap()
        {
            Thread.Sleep(5000);
            window.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
            {
                onMapReadyListener.Invoke();
            }));
        }
    }

 回調時的執行緒

從MahalMap調用傳下來的方法時, 是使用新執行緒執行onMapReadyListener(), 但新的執行緒是不能控制UI物件的. 所以需使用Dispatcher.BeginInvoke() 啟動UI主執行緒執行. 在這個例子比較麻煩的是, 每個類別都有自已的Dispatcher, 但只有MainWindow的Dispatcher才有UI控制權, MahalMap的Dispatcher無法控制TextBox.  所以這就是上述代碼中, 為什麼要把MainWindow的物件一併傳到MahalMap的原因.

請注意一件事, Windows Form在新執行緒與UI主執行緒間的切換, 是使用delegate.BeginInvoke 及EndInvoke. 但到了WPF後, 全部改用Dispatcher. 所以請不要再使用舊的方式.

有參數的委派

如果要委派的方法有返回值或者有參數, 比如
public int onMapReadyListener(int x, int y)
那麼delegate就要寫成 public delegate int OnMapReadyListener(int x, int y)

事件

很多人說,  委派是事件的基礎, 我但實在是看不出來二個有啥差異. 請先看如下代碼, 幾乎跟上面的代碼一樣, 只差在delegate是由MainWindow中使用 += 傳入 MahalMap的

而在MahalMap中, 再增加一個物件變數
public event OnMapReadyListener onMapReadyListener;
另在, 在MahalMap中, 調用onMapReadyListener前, 需先判斷此值是否為null, 以防MainWindow忘記傳下來, 造成NullPointerException.

using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApp1
{
    public delegate void OnMapReadyListener();
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            MahalMap mahalMap=new MahalMap(this);
            mahalMap.onMapReadyListener += onMapReadyListener;
        }
        private void onMapReadyListener()
        {
            txt.Text = "Map is ready";
        }
    }

    class MahalMap
    {
        Window window;
        public event OnMapReadyListener onMapReadyListener;
        public MahalMap(Window window)
        {
            this.window = window;
            new Thread(new ThreadStart(
                delegate
                {
                    Thread.Sleep(5000);
                    if (onMapReadyListener != null)
                    {
                        window.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                        {
                            onMapReadyListener.Invoke();
                        }));
                    }
                }    
            )).Start();
        }
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *