Makale Özeti

SCSF Model View-Presenter pattern'e sadık kalmaktadır. Bu makalede View'larda MVP nasıl uygulandığını ve View'ların kendi aralarında nasıl konuştuğunu inceleyeceğiz.

Makale

Model-View-Presenter


Uygulamalar dinamik olarak yüklenen SmartPart ara yüzlerinden ve bu SmartPart ara yüzlerine taşıyıcılık yapan Workspace nesnelerinden ibaret değildir. Uygulamanın iş kuralları ile ara yüz işlemleri açık kodlama ve anlaşılabilirlik için tamamen bir birinden ayrılmalıdır. Her bir SmartPart ara yüzü sadece ara yüz işlemlerini içermelidir.


Her bir View sadece ara yüz işlemleri gerçekleştirmektedir. Presenter uygulanın iş kodlarının bulunduğu yerdir. View ile Presenter ortak bir Interface aracılığı ile haberleşir. Model ise uygulamanın kabul ettiği veri yapısıdır. Model sınıflar uygulamanın ‘Business Entity’ sınıflarıdır.
[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorView
    {
        ………
        private NavigatorViewPresenter _presenter;
        [CreateNew]
        public NavigatorViewPresenter Presenter {
            set {
                _presenter = value;
                _presenter.View = this;
                _presenter.OnViewReady();
            }
        }
        ……
    }
}
Örnek bir View ait kodları görmektesiniz. View oluşturulurken ObjectBuilder’a kendisine ait Presenter sınıfını oluşturmasını söylemektedir. Oluşturulan Presenter’e View olarak kendisini atamaktadır.
public class NavigatorViewPresenter {        
        private INavigatorView _view;
        public INavigatorView View {
            get {
                return _view;
            }
            set {
                _view = value;
                OnViewSet();
            }
        }
        public void OnViewSet() {
        }

        public void OnViewReady() {………………..
        }
    }
}

SOA

Şimdi örnek uygulamamıza taskbar uygulamasına benzetmeye çalışalım ve MVP pattern iş başında inceleyelim. Öncelikle bize bilgisayar üzerinde ki tüm processleri ve processlere ait detayları verecek bir iş servisine ihtiyacımız var. Daha sonra mavi modül içerisinde tüm processleri listeyen bir ara yüze ve kırmızı modül içerisinde process detaylarını gösteren bir başka ara yüze ihtiyacımız var. Process bilgilerini sağlayan ProcessService’i yazalım:
using System;
using System.Collections.Generic;
namespace Interface.Service {
    public interface IProcessService {
        // seçilen processe ait detay bilgilerini getir
        List<ProcessDetailInfo> EnumProcess(Interface.Service.ProcessInfo info);
        // tüm process bilgilerini getir
        List<Interface.Service.ProcessInfo> EnumProcesses();
    }
}
Evet uygulamamızın Model kısmını oluşturmuş olduk. ProcessInfo ve ProcessDetailInfo sınıfları bizim uygulamamız için Model sınıflarıdır. Projemize iki yeni dll daha eklemeliyiz. Birincisi tüm uygulama genelinde gerekli servislerin uygulandığı Library.dll ikincisi Library.dll içinde ki servislere ait arayüzlerin ve uygulamamızın ‘Model’ kısmını oluşturan sınıfların bulunduğu Interface.dll dir. Kırmızı ve Mavi modül ProcessService’in sadece tanımına yani Interface bilgilerine ihtiyaç duymaktadır.



Uygulama başlarken ProcessService RootWorkItem üzerine ekleyelim ki ProcessService tüm WorkItem nesneleri tarafından kullanılabilir olsun.
namespace Library {
    public abstract class SmartClientApplication<TWorkItem, TShell> : FormShellApplication<WorkItem, TShell> 
        where TWorkItem : WorkItem, new()
        where TShell : Form{

        protected override void AddServices() {
            base.AddServices();
            // Uygulama açılırken ProcessService ilk çağrıldığı yerde 
            // create olacak şekilde register et
            RootWorkItem.Services.AddOnDemand<ProcessService, IProcessService>();
        }
    }
}
Artık istediğimiz yerde ProcessService’e injection ile erişim sağlayabiliriz.
public class NavigatorViewPresenter {
IProcessService _processService;

        [ServiceDependency(Type = typeof(IProcessService))]
      public IProcessService ProcessService {
          set {
                _processService = value;
}
}
      
private INavigatorView _view;
      public INavigatorView View {
          get {
                return _view;
}
set {
                _view = value;
                    OnViewSet();
}
}

public void OnViewSet() { }

public void OnViewReady() {
          List<ProcessInfo> data = _processService.EnumProcesses();
View.DataSource = data;
}
}

public interface INavigatorViewController {
List<ProcessInfo> DataSource {
          set;
}
}

Process listesini gösterecek olan Mavi modüle ait navigator view’dır. Bu view’a ait olan presenter içerisinde ProcessService’i yukarıda ki gibi aldık. Bilgisayarımıza ait process listesini bu servis ile oluşturduk. Daha sonra oluşan process listesini view’a ekranda göstermek üzere gönderdik. View üzerinde ki kodda oldukça basit gelen listeyi bağlamaktan başka bir görevi yok.
[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorViewController
    {
        public NavigatorView()
        {
            InitializeComponent();
        }
        private NavigatorViewPresenter _presenter;
        [CreateNew]
        public NavigatorViewPresenter Presenter {
            set {
                _presenter = value;
                _presenter.View = this;
                _presenter.OnViewReady();
            }
        }
        
        public List<Interface.Service.ProcessInfo> DataSource {
            set {
                bindingSource1.DataSource = value;
            }
        }        
    }
}
Böylelikle bir demonun daha sonuna geldik. Uygulamamız artık bilgisayar üzerinde ki tüm precessleri listelemektedir. Bu uygulama ile her servis güdümlü yazılımın CAB içerisinde nasıl yapıldığını hem MVP pattern CAB içerisinde nasıl kodlandığı gördük.

 

Event Broker


View-Presenter arasında ki iletişimi incelemiştir olduk. Şimdi viewlar arasında ki iletişimi inceleyeceğiz. View’lar birbiri ile nasıl haberleşir? Uygulama genelinde oluşan bir olaya nasıl tepki verilir?



Uygulama içerisinde oluşan her hangi bir aktiviteyi duyurmak için bir Event publish edersiniz. Oluşan aktiviteden haber olmak içinse event’a subscript olursunuz. Demo



Yukarıda son şeklini gördüğümüz örnek uygulamamız üzerinden devam edelim. Kullanıcımız Process listesi üzerinden bir process detaylarını görmek için tıkladığında sağ tarafda ki pencere üzerinde process’e ait detay bilgilerini gösterelim. Önce liste üzerinde kullanıcın tıklamasını yakalayalım.
[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorViewController
    {
        ………………………
        private void bindingSource1_CurrentChanged(object sender, EventArgs e) {
            _presenter.ChangeProcess();
        }
    }
}
View içerisinde logic kod olmayacağı için View bu işi Presenter’a havale etmektedir. Presenter ProcessService’den seçili olan processe ait detayları alacak ve daha sonra bu olayı yayınlayacak.
public class NavigatorViewPresenter {
………
internal void ChangeProcess() {
      List<ProcessDetailInfo> processDetail = _processService.EnumProcess(View.Current);
            OnNeedChangeProcess(processDetail);
}

[EventPublication("NeedChangeProcess", PublicationScope.Global)]
public event System.EventHandler<EventArgs<List<ProcessDetailInfo>>> NeedChangeProcess;
protected virtual void OnNeedChangeProcess(List<ProcessDetailInfo> data) {
if (NeedChangeProcess != null) {
          NeedChangeProcess(this, new EventArgs<List<ProcessDetailInfo>>(data));
           }        
}
    }
}

Oluşan uygulama olayı böylelikle tüm uygulamaya duyuruldu. Eğer her hangi bir WorkItem içerisinde oluşturulan nesnelerden her hangi birinde bu olayı bekleyen bir sınıf var ise bu şekilde haberdar edilmiş olmaktadır. NeedChangeProcess olayı mavi modül içerisinde ki NavigatorViewPresente sınıfı tarafında tüm uygulamaya duyuruldu. Kırmızı modül içerisinde ki ContextViewPresenter sınıfı bu olayı beklemektedir. Oluşan NeedChangeProcess olayı ile ContextView üzerinde process detayları gösterilmektedir.
namespace KirmiziModul.View.ContextView {
    public class ContextViewPresenter {
       
…..
        [EventSubscription("NeedChangeProcess", ThreadOption.UserInterface)]
        public void OnNeedChangeProcess(object sender, EventArgs<List<ProcessDetailInfo>> eventArgs) {
            View.DataSource = eventArgs.Data;
        }
    }
}

[SmartPart]
public partial class ContextView : UserControl, IContextViewController
{
public List<ProcessDetailInfo> DataSource {
         set {
                bindingSource1.DataSource = value;
            }
        }
    }
}

Evet hepsi bu kadar! Artık uygulama genelinde oluşan olaylara uygulamanın nasıl tepki verdiğini görmüş olduk.