C# ile HTML Editörü Yapıyoruz

Html

  Geçtiğimiz günlerde uzun zaman önce geliştirdiğim açık kaynak kodlu projelerime tekrar bir göz attım, biraz nostalji oldu bu aslına bakarsanız 🙂 Projelerim arasında 2003’ün ilk yarısında geliştirmeye başladığım, son sürümü ise 2004 Ocak olan ve SourceForge sitesi üzerinden sunduğum Software Studio‘nun yeri bende ayrıdır, vakit bulup bu projemi devam ettirmeyi çok isterdim. Uygulamam içerisinde Visual Studio’da da kullanılmış olan şekilde ana sayfamda geçmiş projeleri listelemiştim;

Software Studio başlangıç sayfası

   Ekran görüntüsünde sağ tarafta Start Page segmesinde gördüğünüz bölüm aslında uygulamaya gömülü bir browser içerisinde sunulan bir html sayfası. Üstelik bu html sayfası içerisinde bulunan "Open File" ve "New File" butonları vasıtasıyla host uygulamaya komut gönderebilmekte, açılışında host uygulamadan veri çekebilmekte. Ekran görüntüsünde gördüğünüz geçmiş dosyalar listesi ana uygulama içerisinde yer alan bir fonksiyonun html sayfasındaki bir javascript koduyla çağırılması sonucunda oluşmaktadır. Bu projeyi geliştirdiğim zamanlarda .Net içerisinde gelen bileşenler arasında maalesef ki şimdi olduğu gibi bir Web tarayıcısı yoktu ve sizin bu bileşeni kullanabilmeniz için ActiveX’ler ile sık sık muhatap olmanız gerekiyordu. Geçen zamanda şanslıyız ki artık .Net Framework ile birlikte bir web tarayıcı bileşeni de gelmekte ve pek çok işi bizim için yapmakta. Bu makalemde öncelikle sizlerle web tarayıcı bileşenini kullanarak nasıl ana uygulamanız ile sunmuş olduğunuz web sayfası arasında iletişimi sağlayabileceğinizi paylaşıyor, ardından da konuyu biraz daha öteye taşıyarak bu bileşen yardımıyla nasıl aşağıdaki ekran görüntüsünde yer alan gibi bir HTML editörü yapabileceğinizi (evet, yanlış okumadınız) paylaşıyor olacağım.

Software Studio html editorü

   Uygulama arayüzünüze sürükle-bırak ile ekleyebileceğiniz WebBrowser bileşeni sayesinde uygulamanıza html dokümanlarını görüntüleme özelliği yanında tam anlamıyla bir web deneyimini taşıma şansına sahipsiniz. WPF’in henüz olmadığı zamanlarda .Net Framework’e eklenmiş olan bu bileşen, masaüstü uygulamalarıyla kıyaslandığında, web’in canlı ve dinamik yapısı göz önüne alınırsa Windows Forms uygulamaları için önemli bir yenilikti. Doğru kullanıldığında kullanıcıya daha iyi bir deneyim sunabilecek kapasiteye sahip olan bu bileşen pek çok uygulamaya da hızlıca adapte edildi. Bu adaptasyonun bu kadar hızlı ve kolay olmasının altında yatan neden WebBrowser bileşeninin ObjectForScripting özelliği kullanılarak html ve uygulama arasındaki  iletişimin kolayca sağlanıyor olmasıdır. ObjectForScripting özelliğini nesne kabul etmesi nedeniyle uygulamanızdaki herhangi bir sınıfın, hatta ana uygulama penceresini bile, html sayfasından ulaşabilir yapabilmeniz mümkün. Tek dikkat edilmesi gereken nokta ObjectForScripting özelliğine atayacağınız sınıfın COM tarafından görülebilir (ComVisibleAttribute) olarak işaretlenmiş olmasıdır. WebBrowser bileşeninin daha önce bizlerin ActivX ile yaptığımız işlemleri arkaplanda bizim adımıza yaptığını düşünecek olursak, vereceğimiz sınıf örneğinin html üzerinden kullanılabilmesi için COM görülebilir olarak işaretlenmesi gereğinin nedenini de anlayabiliriz. Aşağıda örnek olması adına sizinle paylaştığım ve html ile uygulama arasında veri transferinden sorumlu olan bir sınıfı bulabilirsiniz;

[ComVisible(true)]
public class TarayiciEntegrasyonu {
    public string UygulamaSurumu { get; private set; }

    public TarayiciEntegrasyonu() {
        UygulamaSurumu = GetType().Assembly.GetName().Version.ToString();
    }

    public void YeniDosya() {
        /*Yeni Dosya oluşturma iş mantığı kodları*/
        MessageBox.Show("Yeni dosya oluşturuldu");
    }
}

    Örnek sınıfımız, en basit şekliyle, içinde bulunduğu assembly’nin sürüm bilgisini sunmakta. Aşağıda kodu kullanarak bu sınıfı formumuzda bulunan web tarayıcı bileşenimize tanıtabiliriz;

webBrowser.ObjectForScripting = new TarayiciEntegrasyonu();
webBrowser.Navigate(acilisSayfasi);

   Bu noktadan sonra script nesnemizi html içerisinde kullanabilmek için biraz javascript kodu gerekli olacak. WebBrowser bileşeni içerisinde sunulan html sayfalarından harici uygulama nesnemize  ulaşabilmek için HTML DOM içerisinde window nesnesi altında yer alan external özelliği sunulmaktadır. Aşağıda yer alan basit web sayfası, html yüklendiğinde onload olayını tetikleyerek, bu olayı dinleyen SurumBilgisiniYaz fonksiyonunu çağıracaktır. SurumBilgisiniYaz fonksiyonu ise versiyon id’li label bileşeninin içeriğine uygulama sürüm bilgisini yazmakta.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title></title>
    <script type="text/javascript" language="javascript">
        function SurumBilgisiniYaz() {
            this.versiyon.innerText = "Uygulama Sürümü : " + window.external.UygulamaSurumu;
        }

        function YeniDosya() {
            window.external.YeniDosya();
        }
    </script>
</head>
<body onload="SurumBilgisiniYaz">
    <p>
        <label id="versiyon">
        </label>
    </p>
    <input type="button" value="Yeni Dosya" onclick="YeniDosya();" />
</body>
</html>

Etkileşimli html açılış sayfası

    Benzer şekilde sayfada yer alan "Yeni Dosya" butonuna tıklandığında TarayiciEntegrasyonu sınıfı YeniDosya fonksiyonu tetiklenerek ilgili iş mantığının çalışması sonrası kullanıcı bir mesaj kutusu ile bilgilendirilmekte;

Uygulama fonksiyonlarını kullanabilen html dokümanı

    WebBrowser bileşeninin ObjectForScripting özelliğini kullanarak html içerisinden tek taraflı olarak uygulamamıza erişimi tanımlamış oluyoruz. Uygulamamız üzerinden web browser içeriğiyle etkileşimde bulunabilmek için bir kaç yöntem bulunmakta. Bu yöntemlerden ilki ve en basiti, WebBrowser bileşeni Document özelliğini kullanarak HML DOM’a müdahale etmek. Aşağıdaki örneğimizde sayfamızı siyah ardalan üzerine beyaz font ile göstermek için gerekli kod parçacığını bulabilirsiniz;

Html doküman özellikleri uygulama içerisinden değiştirilebilir

webBrowser.Document.BackColor = Color.Black;
webBrowser.Document.ForeColor = Color.White;

   Kullanıcı ile daha iyi bir etkileşim kurabilmek ve daha canlı bir içerik sunabilmek adına yukarıdaki yöntem dışında javascript kullanımı da mutlaka gerekecektir. WebBrowser bileşeni içerisinden bir html sayfasındaki javascripti fonksiyonunu çalıştırmak için InvokeScript  fonksiyonu kullanılabilir. Kullanımı son derece basit olan ve çalıştırılacak script adı ile parametrelerinin verildiği bu fonksiyon için aşağıda örnek bir kullanım bulabilirsiniz. Örneğimizde başta paylaştığım html sayfamız kullanılmakla birlikte body elementi içerisindeki onLoad özniteliği kaldırılmış, dolayısıyla da açılışta SurumBilgisiniYaz fonksiyonunun çalışması engellenmiştir.

webBrowser.Document.InvokeScript("SurumBilgisiniYaz");

   Gördüğünüz gibi ObjectForScripting, Document ve InvokeScript kullanılarak rahatlıkla sunduğumuz içeriğimiz ile uygulamamız arasında çift yönlü bir iletişimi rahatlıkla kurabilmekteyiz. WebBrowser içeriği ile çift taraflı etkileşimi gördükten sonra isterseniz şimdi daha eğlenceli olan konumuza geçelim; WebBrowser bileşenini HTML Editörü olarak kullanmak.

WebBrowser bileşeni içerisindeki html dokümanı düzenlenebilir hale geldi

   WebBrowser’ımızı html editörü olarak kullanabilmek için öncelikle içeriğin düzenlenebilir olarak işaretlenmesi gerekiyor. Bunu yapabilmek için bir kaç yöntem bulunmakta. İlk iki yöntemimiz bileşenin arkaplanda kullandığı ActivX hakkında bilgisi olanlara yönelik; ama merak etmeyin, üçüncü yöntemimiz çok daha kolay 🙂

   WebBrowser bileşeni Document özelliği içerisinde yer alan DomDocument özelliği istediğimiz şekilde düzenlenebilir bir kontrole sahip olma noktasında giriş noktamız olacaktır. Arkaplanda kullanılan unmanaged bileşeni gösteren ve bir nesne veren bu özellik, arkada yapılanları ve kullanılması gereken arayüzleri bilmeniz durumunda oldukça işinize yarayacaktır. mshtml.IHTMLDocument2 arayüzünde olan bu değişkeni içerisinde yer alan designMode değerinin string "On" yapılması ile birlikte artık içeriğimiz düzenlenebilir olacaktır. Bunu yapabilmenin ilk yolu bu arayüzün bulunduğu bir ActivX wrapper’ı oluşturarak DomDocument özelliğini arayüze cast etmek ve ardından da designMode özelliğine "On" değerini atamaktır. Bu yöntemdeki sıkıntı ilgili wrapper’ı oluşturmak ve her deployda istemcinize bu wrapper’ı da göndermek zorunda olmanız. Gerekmediği sürece bu tarz bir işe girmeyi sevmediğimde, benimle benzer düşüncede olanlar için, ikinci bir alternatifi sizinle paylaşmak istiyorum; bu değişkene reflection üzerinden erişerek değerini değiştirmemiz de mümkün;

var domDocument = webBrowser.Document.DomDocument;
var designModeProperty = domDocument.GetType().GetProperty("designMode");

designModeProperty.SetValue(domDocument, "On", null);

   Herhangi bir ek assembly ve bağımlılığı olmadığı için tercih ettiğim ikinci yöntem dışındaki üçüncü alternatifimiz ise çok daha kolay; IHTMLElement3 arayüzü ile birlikte bize sunulan contentEditable özelliğini kullanmak. İşletim sisteminde Internet Explorer 8 ve üstü bir web browser’ın kurulu olmasını gerektiren bu alternatifte sayfamız üzerindeki bir bileşeni (örneğin bir div’i) düzenlenebilir yapabiliyoruz. Bu özelliğe body elementi için true değerinin atanması WebBrowser bileşenine aksi belirtilmediği sürece tüm sayfanın düzenlenebilir olacağını anlatacaktır. Html dokümanı üzerindeki body elementine bu özelliği atamak değerini değiştirmek için yukarıda sizinle paylaştığım Document özelliği altındaki alt özellikleri kullanarak yapabilirsiniz;

webBrowser.Document.Body.SetAttribute("contenteditable", "true");

   Bu yöntem ile ilgili bilinmesi gereken önemli bir nokta sayfanın içeriğine müdahale edildiğidir. Dolayısıyla sayfa bu haliyle saklanacak olursa destekleyen web tarayıcıları içerisinde de düzenleme yapılabilir 🙂

   Yukarıda sıraladığım adımlar ardından Html dokümanımızı WebBrowser bileşeni üzerinden düzenlenebilir hale getirdik; ama şimdiye kadar yaptıklarımızla mevcut yazıları değiştirmek ya da içeriği silme gibi işlemler yapılabilmekte. Gerçek bir Html editöründe kes, yapıştır, geri al, tümünü seç v.b. komutların çalıştırılabilir olması, seçili olan bileşen hakkındaki bilgileri alabiliyor olmamız gerekli. Şimdi isterseniz birlikte bunları nasıl yapabileceğimizi görelim;

   WebBrowser bileşenine yüklediğimiz bir html dokümanı üzerinden kes, kopyala, geri al v.b. işlemleri yaptırmak için gerekli parametreleri de vererek doğru komutları göndermemiz gerekli. Bu komutlarımızı göndermek için Document özelliği içerisine yerleştirilmiş olan ExecCommand fonksiyonu kullanılmalıdır. Parametre olarak komut adı, arayüz gösterilip gösterilmeyeceği ve komuta gönderilecek değeri vererek kullanabileceğiniz ExecCommand komutuna ait örnek kullanımları aşağıda bulabilirsiniz;

//seçili metin'i kırmızı yapar
webBrowser.Document.ExecCommand("ForeColor", false, "red");

// verilen id ile bir buton oluşturur
webBrowser.Document.ExecCommand("InsertButton", false, "buton1");

//son işleri geri alır
webBrowser.Document.ExecCommand("Undo", false, null);

//geri alınan işleri yeniden yapar
webBrowser.Document.ExecCommand("Redo", false, null);

//Seçili metini keser
webBrowser.Document.ExecCommand("Cut", false, null);

//seçili metini kopyalar
webBrowser.Document.ExecCommand("Cpoy", false, null);

//hafızada bulunan veriyi yapıştırır
webBrowser.Document.ExecCommand("Paste", false, null);

   Geniş bir yelpazede komut kütüphanesine sahip olan WebBrowser bileşeninde kullanabileceğiniz tüm komutlar ile bu komutların parametrelerine http://msdn.microsoft.com/en-us/library/ms533049%28v=vs.85%29.aspx adresinden ulaşabilirsiniz.

   Html dokümanına komut göndermeyi de başardıktan sonra yapmamız gerekenler iyice azaldı. Bir Html editörü seçili olan bileşen hakkında bilgi de verebilmeli ya da en azında desteklediği komutlara göre arayüzünü güncelleyebilmeli, örneği; bir resim seçili ile kalınlaştır (bold) butonunun aktif olmaması gerekli.

   Bu işlevselliği destekleyebilmek için öncelikle editörümüzde aktif olarak seçili bileşenin değiştiğinden haberdar olmamız gerekli. Bunu yapabilmek için Document özelliği altında bulunan AttachEventHandler fonksiyonu kullanılarak seçim değiştiğinde bir fonksiyonumuzun tetiklenmesini sağlayabiliriz. AttachEventHandler fonksiyonu takip edilecek olan olay adını string olarak almaktadır, bizim örneğimiz için kullanılması gereken değer onselectionchange‘dir.

webBrowser.Document.AttachEventHandler("onselectionchange", webBrowser_SelectionChange);

private void webBrowser_SelectionChange(object sender, EventArgs e) {
    // Seçili bileşen değişti
    // ilgili komutların durumları kontrol edilerek 
    // arayüz güncellenmeli
}

   Artık aktif kontrolü değiştiğinden haberdarız, sırada arayüz bileşenlerimizin durumlarını güncellemek var. Bunu için yapılması gereken her bir komut için durum sorgusu yaparak seçili bileşen için desteklenip desteklenmediğine göre aktif/pasif  hale getirmektir. Durum sorgulama fonksiyonumuz, aynı tasarım moduna geçmemizi sağlayan designMode değeri gibi, DomDocument özelliği içerisindedir; queryCommandState. Parametre olarak durumu sorgulanması istenen komutu vermeniz gereken bu fonksiyon size yanıt olarak desteklenmesi durumunda true, aksi hallerde false değeri dönecektir. queryCommandState fonksiyonuna benzer şekilde aktif dokümanın değerinin sorgulanması da mümkündür. Bu durumda kullanılması gereken komut ise queryCommandValue fonksiyonudur. Aşağıda reflection ile sıkça yapılacak olan ve bu iki komutu kullanmamızı sağlayan kodları bulabilirsiniz. Bu kodları kullanım kolaylığı olması adına bir genişleme (extension) fonksiyon olarak tanımlayarak hazırlayarak aşağıda sizlerle paylaşıyorum;

public static class HtmlDocumentExtensions {
    public static object queryCommandValue(this HtmlDocument document, string komut) {
        var domDocument = document.DomDocument;
        var queryCommandValueMethod = domDocument.GetType().GetMethod("queryCommandValue");

        return queryCommandValueMethod.Invoke(domDocument, new[] { komut });
    }

    public static bool queryCommandState(this HtmlDocument document, string komut) {
        var domDocument = document.DomDocument;
        var queryCommandValueMethod = domDocument.GetType().GetMethod("queryCommandState");

        return (bool)queryCommandValueMethod.Invoke(domDocument, new[] { komut });
    }
}

   Bu genişleme fonksiyonlarını aşağıdaki şekilde kullanabilirsiniz;

var fontAdi = webBrowser.Document.queryCommandValue("FontName");

var geriAlDestekliyorMu = webBrowser.Document.queryCommandState("Redo");

    Şimdiye kadar sizlerle paylaştığım bilgilerle uygulamanızda sunduğunuz web sayfaları ile çift yönlü iletişim kurabilir, bir Html editörü hazırlayabilirsiniz. Bileşen tarafından bize sunulan kes, kopyala, yapıştır, geri al v.b. komutlar bazı ileri kullanımlarda ihtiyaçlarınızı tam anlamıyla karşılayamayabilir. Bu gibi durumlarda javascript bilginizi de kullanarak doküman içerisine bir script enjekte edebilir, ardından da InvokeScript fonksiyonu ile bunları çalıştırabilirsiniz. Bu yöntemin de işe yaramadığı durumlarda ise doğrudan Html DOM’una müdahale ederek string olarak oluşturacağınız html içeriğine DOM’a ekleyebilirsiniz (örneğin ilgili element içerisindeki innerHTML özelliğini kullanarak). Net Framework WebBrowser bileşeni arkaplanda ActiveX bileşenleri kullanıyor olması nedeniyle sık sık takılacağınız bir durumla karşılaşabilirsiniz. Bu durumlarda MSDN sitesi üzerinde bulunan dokümanlar yardımcınız olacaktır. Bu dokümanların tek sıkıntısı managed kod için yazılmamış olduklarından dolayı uygulamaya geçirirken sizleri uğraştırabilecek olmalarıdır.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# konusundan Microsoft tarafından dört yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz.
vCard - Twitter - Facebook - Google+

2 yorum

  1. kernel32   •  

    Merhabalar,
    Bu şekilde bir editör tarzı birşey yaptım ancak bir sorum olacak sağlıklı veriler bulamadım internette.
    HTML Table oluşturma hakkında nasıl oluşturabilirim. Teker teker table tr ve td leri 3 er satırlık bir kod var onla oluşturmak zorundamıyım aklınızda paylaşabileceğiniz bir method varmıdır?
    Teşekkürler iyi çalışmalar…

    • kernel32   •  

      Tablo işini hallettim gerek kalmadı ancka şimdide tabloda şöyle bir sıkıntım var çözümünüde yok sanırım. Tablo oluşturunca editmode da sadece dış çerçevenin boyutu oyanıyor içerideki direkleri ve yatay katları oynamak istiyorum olmuyor ve bir sıkıntı daha border olarak 0 verince hiç görünmüyor browser görüntüsü olduğu için bunlar için bir çözüm varmıdır?

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir