9 Ocak 2008 Çarşamba

Smart Client Software Factory Arayüz İşlemleri

Shell - Module

Bu makaleye başlamadan önce arka alanda bizim için çalışan Injecton Pattern ve ObjectBuilder sihirbazını incelemenizi öneririm. Anlamamız gereken iki kavram vardır ‘Module’ ve ‘Shell’. Modüller bir birinden bağımsız çalışan içinde kob bloklarımız, kullanıcı ara yüzlerimiz, iş servislerimiz, ara yüz parçalarımız ve çeşitli bileşenlerimiz olan kendi başına ayrı uygulamalardır. Modül kendi başına çalışan ayrı bir .NET projesi gibi görülebilinir. Modüller kendi başlarına atomik bir işi yerine getirirler. İkinci kavramımız ‘Shell’ kavramıdır. Shell basitçe ‘ortak pencere’dir. Shell bizim modüllerimizi ve ara yüz elemanlarımızı taşır. Shell ve müdüllerin hiç biri bir diğerini referans etmez. Tanım olarak;

Module: Karmaşık arayüzler kullanan tek başına bir projedir

Shell: Karmaşık ara yüzler için bir sunucu(host) uygulamasıdır.

Basit bir uygulama ile bu iki kavramın nasıl çalıştığına bakalım. Sadece kırmızı bir form üreten projemiz olsun. Bu projeden tamamen bağımsız çalıştığında mavi bir form üreten ikinci bir projemiz olsun. Bunlar bizim modüllerimiz. Üçüncü bir projede ise bu iki modülü taşıyacak olan Shell uygulamamız olsun. Shell sadece bu iki formu görüntüleyecektir.

Burada zor olan her üç uygulamanın aynı Solution üzerinde çalışıyor olmasıdır. Biz her üç formunda açılışta gösterilmesini istiyoruz. Ortada hiçbir referans olmadığı için reflection ile tabii ki her üç formu gösterebiliriz. Fakat biz bu işi CAB bırakıyoruz.

Önce üç adet .NET projesi oluşturalım. Shell ve modüllerimiz Mavi, Kırmızı. Her üç uygulamaya da ObjectBuilder, CAB ve CABWinforms dll’lerini referanslarını ekleyelim. Tüm uygulamalarda ‘Build Directory ‘ alanını aynı konum ayarlayalım ki bir arada çalışabilsinler. Her bir uygulamayı çalıştırdığımızda kendi özel formlarını görürüz.

Şimdi Shell uygulamasına diğer uygulamalara ‘Container’ olma yeteneğini verelim. Bunun için Shell uygulamasını CAB uygulamasında türetmemiz ve Main fonksiyonu içerisinde üst sınıf olan CAB uygulamasını başlatmamız yeterlidir.

[cs]

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.CompositeUI;
 
namespace Shell {
    public class Program :FormShellApplication {
        [STAThread]
        static void Main() {
            new Program().Run();
        }
    }
}

CAB uygulama sınıfı FormShellApplication dır. İki generic parametresi vardır. Giriş bölümde tartıştığımız Use-Case diyagramlarımızı yerine getiren WorkItem sınıfı, diğeri bizim uygulamamızda ara yüz işlemlerini yerine getiren ve ortak pencere olma özelliğini taşıyan Form1 formumuzdur. CAB Shell uygulaması bir xml dosya içerisinde kendisine çalıştırılacak modüllerin sunulmasını bekler. Modül listesini taşıyan dosya uygulamanın çalışma dizininde yer alan ‘ProfileCatolog.xml’ dosyasıdır. Bu dosya çok basit bir şekilde çalıştırılacak mödül listesini gösterir.

<?xml version="1.0" encoding="utf-8" ?>
<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile">
  <Modules>
    <ModuleInfo AssemblyFile="KirmiziModul.exe" />
    <ModuleInfo AssemblyFile="MaviModul.exe" />
  </Modules>
</SolutionProfile>

Son bir işimiz daha kaldı. Uygulama programını CAB uygulama sınıfından türettiğimiz gibi modül programlarını da CAB ile uyumlu hale getirmeliyiz.

CAB uygulaması Run() fonksiyonu ile çalıştırıldığında CAB ProfileCatolog.xml dosyasını acar. Burada yer alan modüllerde CAB ModuleInit sınıfından türetilmiş bir sınıfı arar. Eğer ModülInit sınıfını bulursa ModuleInit.Load() fonksiyonunu çağırarak modülleri yükler.

[cs]

using Microsoft.Practices.CompositeUI;
 
namespace KirmiziModul {
    public class KirmiziModuleInit : ModuleInit{
        public override void Load() {
            base.Load();
            Form1 form = new Form1();
            form.Show();
        }
    }
}

ModuleInit içerisinde biz bu örnekte formlarımız gösteriyoruz. Shell uygulamamızı çalıştırdığımızda her üç formunda açıldığını görüyoruz.

Hepsi bu kadar böylelikle ilk CAB uygulamızı yazmış olduk.

CAB uygulamalarının görsel her bir parçası [SmartPart]’dır. SmartPart bir UserControl nesnesidir. Her hangi bir temel sınıftan türetilmeleri gerekmemektedir. Sadece [SmartPart] attribute ile işaretlenmeleri yeterlidir. Bu parçalar uygulama içerisinde dinamik olarak yerleşmektedir. Dizayn anında uygulama içerisinde [SmartPart] nesnelerini yerleşeceği alanlar Workspace olarak adlandırılır. Workspace nesneleri IWorkspace ara yüzünü uygulayan ve içerinde [SmartPart] özelliğine sahip nesneleri saklayan Container nesnelerdir.

Böylelikle uygulamanız için en esnek ara yüz tasarımlarını gerçekleştirebilirsiniz. Ayrıca IWorkspace arayüzünü uygulayarak kendi SmartPart Container kontrolü olan Workspace sınıflarını yazabilirsiniz. RootWorkItem Injection Pattern kullanarak oluşan tüm nesneleri takip edebilmektedir. Dolayısı ile sizin tasarım anında ara yüze yerleştirdiğiniz Workspace nesneleri oluşturulurken RootWorkItem.WorkSpaces koleksiyonu üzerine eklenecektir. Şimdi uygulama üzerinde işler nasıl dönüyor inceleyelim. İlk demo uygulamada kırmızı ve mavi adlı iki ayrı uygulamayı aynı proje ile açmıştık. Şimdi bu iki ayrı uygulamayı aynı ara yüz içerisinde gibi bir araya getirelim. Öncelikle Shell form üzerine bir tane DeckWorkspace kontrolü ekleyelim. Uygulama genelinde gerekli genel ara yüz işlemleri için bir Layout modülü oluşturalım. Daha sonra bu workspace içerinde uygulamanın asıl görünümünü oluşturacak bir ShellLayoutView ekleyelim.

Şekil 1 Shell.exe içinde ShellForm


Şekil 2 Layout.dll için ShellLayoutView

ShellLayoutView tüm uygulama için ortak olan ara yüz elemanlarını barındırmaktadır. Daha sonra uygulama içerisinden ortak ara yüz elemanlarına nasıl erişim sağlandığını inceleyeceğiz. ShellForm üzerinde ki LayoutWorkspace RootWorkItem.Workspaces içerisine kendiliğinden eklenecektir. Layout modül içerisinde ki ShellLayoutView ara yüzünü LayoutWorkspace içerisine ekleyelim.

[cs]

namespace Layout {
    public class Module : ModuleInit {
        private WorkItem rootWorkItem;
        [InjectionConstructor]
        public Module([ServiceDependency] WorkItem rootWorkItem) {
            this.rootWorkItem = rootWorkItem;
        }

        public override void Load() {
            base.Load();

            // layout view shellform üzerine ekle
            ShellLayoutView shellLayout = rootWorkItem.SmartParts.AddNew();
            rootWorkItem.Workspaces["LayoutWorkspace"].Show(shellLayout);
        }
    }
}

Şimdi her iki iş modülümüz içinde ki ara yüzleri ayrı pencereler içine yerleştirelim.

[cs]

namespace KirmiziModul {
    public class KirmiziModuleInit : ModuleInit{
        private WorkItem _workItem;
        [InjectionConstructor]
        public KirmiziModuleInit([ServiceDependency] WorkItem workItem) {
            _workItem = workItem;
        }
        public override void Load() {
            base.Load();
            // Form1 view shellformview üzerinde adı 
            // 'ContextWorkspace' olan alan üzerine ekle
            Form1 kirmiziView = _workItem.SmartParts.AddNew();
            _workItem.Workspaces["ContextWorkspace"].Show(kirmiziView);
        }
    }
}


namespace MaviModul {
    public class MaviModuleInit :ModuleInit {
                
        private WorkItem _workItem;
        [InjectionConstructor]
        public MaviModuleInit([ServiceDependency] WorkItem workItem) {
            _workItem = workItem;
        }
        public override void Load() {
            base.Load();
            // MaviModule.Form1 view shellformview üzerinde adı 
            // 'NavigatorWorkspace' olan alan üzerine ekle
            Form1 kirmiziView = _workItem.SmartParts.AddNew();
            _workItem.Workspaces["NavigatorWorkspace"].Show(kirmiziView);
        }
    }
}

Gördüğünüz ara yüzü her bir parçası farklı bir dll dosyası içerisindedir. Tamamen dinamik olarak oluşturulduk.