Başka Uygulamalarla Çalışmak
Bu bölümde hem diğer uygulamalar(Hesap makinesi, Spotify v.s) nasıl çalıştırılır ona bakacğaız, hem de daha önemlisi, diğer programlama dilleriyle yazılmış kodları nasıl çalıştırırız ona bakacağız.
İlk olarak basit bir uygulama nasıl açılıyor ona bakalım. VBA'de Shell komutu ile yaptığımız bu işi .Net'te Process sınıfı ile yapıyoruz. Bunu yapmanın da birkaç yolu var. Hepsine bakalım.
Bu arada bu örneği, ayrı bir proje yapmak yerine aşağıdaki InvestPY projesi içine koymayı uygun gördüm.
İlk yöntem
Notepad ile bir metin dosyasını açacağız. Process sınıfının Start metodu ile programın adını ve parametreleri string olarak veriyoruz. En basit hali budur.
using System.Diagnostics; //bahsekonu sınıf bu namespace içinde
Process.Start("calc"); //exe uzantısına gerek yok
Process.Start("notepad.exe", @"E:\OneDrive\Dökümanlar\GitHub\dotnet\Ugulamalar\InvestPY\myinvestpy.py");
İkinci Yöntem
Process sınıfından bir nesne yaratıp, parametreleri de aşağıdaki gibi belirliyoruz. Bu yöntemle Process sınıfının çok daha zengin üye listesine erişebiliyoruz.
Process notePad = new Process();
notePad.StartInfo.FileName = "jupyter";
notePad.StartInfo.Arguments = "notebook";
notePad.Start();
Üçüncü Yöntem
ProcessStartInfo sınıfını da devreye sokuyoruz. Önce StartInfo bilgilerini oluşturuyoruz, sonra usign block'u içinde Process sınıfını devreye alıyoruz. İkinci yöntemle arasındaki fark için buraya bakınız. Ancak çoğunlukla ilk yöntem bile yeterli olacaktır.
ProcessStartInfo start = new ProcessStartInfo();
start.Arguments = "www.excelinefendisi.com";
start.FileName = @"C:\Program Files (x86)\Microsoft\Edge\Application\msedge";
int exitCode;
using (Process proc = Process.Start(start))
{
proc.WaitForExit();
exitCode = proc.ExitCode;
MessageBox.Show(exitCode.ToString()); //sorunsuz ise 0 çıkar
}
Bu sefer, işleri biraz daha ileri götürüp, başka bir programlama diline yazılmış bir scripti çalıştırıp Excel'le nasıl bağlantı kurarız onu göreceğiz. Aslında yukarıdaki mantıktan pek de bir farkı yok. FileName olarak ilgili programlama dilinin derleyicisini, argument olarak da gerekli tüm argümanları verceğiz. Bu argümanlar içnde çalışıtırılacak script dosyası ve bu dosyaya dışardan verilen parametreler de dahil olacak.
Bu ilk örneğimizde Excel'deki tüm işi Python'a yaptıran bir kodumuz olacak. Bunun için PC'mizde python kurulu olması gerekiyor. Bu vesileyle Python öğrenmediyseniz bunu da mulatka öğrenmenizi tavisye ederim. Hem öğrenmesi çok kolay bir dil, hem de çok güzel işler yapılabiliyor. Özellike Veri Bilimi, Makine Öğrenimi ve Yapay Zeka konularında önde gelen dildir. Excel DNA sayfasında gördüğümüz gibi pyxll isimli kütüphane ile de Excel için XLL add-in'ler yazılabilir.
Bu projede, https://www.investing.com/ adresinden çeşitli ekonomik verileri çeken bir python kodumuzu var. Python'da webden veri çekme ile ilgili olarak BeautifulSoup diye bir kütüphane var ancak şanslıyız ki birileri bu siteden veri çekecek bir kütüphane(API) yazmış bile, o yüzden python'da çok basit bir kod yazdım. Ancak diğer web siteleri için böyle hazır api olmayabilir, o yüzden BeautifulSoup kullanmak gerekirdi. Kodun çalışması için PC'mizde pandas, investpy ve openpyxl adlı python kütüphanelerinin de kurulu olması gerekmektedir.
Kod, özetle bu siteden Türkiye'ye ait çeşitli tipteki yatırım araçları için ilk 10 kıymete ait biglileri getiriyor, ve bunları Excel'e yazdırıyıor. Bu kısmı tamamen Python yapıyor. Bizim Excel'de sunduğumuz fonksiyonalite ise kullanıcıya çeşitli bilgileri Ribbon'dan girdirmek, bir nevi programı kullanmak için kullanıcıya arayüz sağlamak. Kullanıcıların hiç python bilmememsi, tüm python dosyalarını sizin hazırladığınız bir durumda kullanıcılara python kurdurtmak, sonrasında kütüphane kurdurtmak da ayrı bir sorun olabilir. O yüzden buraya isterseniz, kullanıcıya pythonı indirip kurulumu yaptıran, kütüphaneleri indirtmeyi ve python path'ini öğrenmeyi sağlayan butonlar da koyabilirsiniz. Bunu bir ara ödev olarak hazırlayıp aşağı Ödevler bölümüne koyacağım.
Çalıştırdıktan sonra şöyle Ribbonumuz şöyle görünür:
Farkettiyseniz Ribbondaki text kutuları oldukça kısa olup girdiğimiz tüm metin burda görünmüyor. Bunun yerine Invest group'unun sağ altındaki dialog launcher ile açılan Settings formuna da bu bilgiler girilebilir. Bunu ödev olarak düşünebilirsiniz. Ben formu proje dosyası içine dahil ettim, siz sadece gerekli ayarlamaları yapın.
Çalışmayı, python kodu da dahil olacak şekilde buradan indirebilirsiniz.
Şimdi kodlara bakıp sonra açıklamasına geçelim.
public partial class Ribbon1
{
Process process;
bool iptal = false;
private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
//ilk değer atamaları
this.editBox1.Text = DateTime.Today.AddDays(-10).ToString("dd/MM/yyyy").Replace(".", "/");
this.editBox2.Text = DateTime.Today.ToString("dd/MM/yyyy").Replace(".","/");
this.editBox3.Text = @"C:\Invest\sonuclar.xlsx"; //bu ve alttaki settings formuna koyarak da yapılabilir, siz böyle deneyin
this.editBox4.Text = @"C:\Users\volka\AppData\Local\Programs\Python\Python38\python.exe";
}
private async void button1_ClickAsync(object sender, RibbonControlEventArgs e)
{
Globals.ThisAddIn.Application.StatusBar = "Lüfen bekleyiniz...";//daha görünür olması için A1'e de yazdırabiliriz
iptal = false;
string mesaj = await fetchdata();
Globals.ThisAddIn.Application.StatusBar = "";
if (iptal)
{
MessageBox.Show("İptal edildi");
}
else
{
Globals.ThisAddIn.Application.Workbooks.Open(this.editBox3.Text);
if (mesaj.Length < 100)
MessageBox.Show(mesaj);
else
{
Excel.Worksheet ws = Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets.Add();
ws.Name = "Sonuç-Hata mesajı";
ws.Cells[1, 1] = mesaj;
}
}
}
private async Task fetchdata()
{
string sonuc = await Task.Run(() =>
{
string pyfile = @"E:\OneDrive\Dökümanlar\GitHub\dotnet\Ugulamalar\InvestPY\myinvestpy.py";
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = this.editBox4.Text;
start.Arguments = pyfile + " " + this.editBox1.Text + " " + this.editBox2.Text + " " + this.editBox3.Text;
start.UseShellExecute = false;
start.CreateNoWindow = true;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true;
using (process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string stderr = process.StandardError.ReadToEnd();
string result = reader.ReadToEnd();
if (string.IsNullOrEmpty(stderr))
return string.Format("Sonuç:{0}", result);
else
return string.Format("Hata:{0}", stderr);
}
}
});
return sonuc;
}
private void button2_Click(object sender, RibbonControlEventArgs e)
{
process.Kill();
iptal = true;
Globals.ThisAddIn.Application.StatusBar = "";
}
}
Öncelikle şunu belirtmek isterim ki, burdaki async/await kullanımı bu konuyu anlamak için de güzel bir fırsat oldu. Siz tabiki bu ifadelerin teorisini iyice araştırmalısınız. Burda yapmaya çalıştığım, data gelene kadar Excel kitlenmesin ve kullanılabilir durumda olsun. Keza, bir şekilde işlem çok uzun sürecek gibi olursa da işlemi İptal butonuyla iptal edebilelim. Keza, o sırada diğer Process group'undaki butonları da çalıştırabilirsiniz.
Programın esasına gelecek olursak, Excel'in sunduğu ribbon arayüz(veya settings form) aracılığı ile kullanıcıyı Python Console'da çalışmaktan kurtartmış ve aşina olduğu Excel'de kalmasını sağlıyoruz. Parametreleri girip Getir butonuna bastıktan sonra Python kodu çalışıyor. Biz burada ilk başlıkta gördüğümüz 3. yöntemi kullanmış olduk. Yukarıdaki örneklerden farklı olarak gördüğümüz satırlar ve anlamları şöyle:
- UseShellExecute özelliğine false atadık. Normalde default değer true'dur ve bu bizim VBA'de de bildiğmiz Shell komutunu çalıştırmaya denk veya daha genele ifadeyle cmd komut satırından ilgili komutun verilmesine denk. Ancak biz geriye bir değer(python'daki print komutlarının sonucu) döndüreceğimiz için Shell kullanmayacağız, onun yerine CreateProcess fonksiyonunun çalışmasını istiyoruz. Bu ikisi arasındaki farka ait detay bilgiye buradan ulaşabilirsiniz.
- CreateNoWindow = true diyerek, bir pencere açılmasını istemiyoruz.
- RedirectStandardOutput ve RedirectStandardError değerlerine true atayarak hem çalışan programın döndüreceği çıktıyı(Python'da print ifadeleri) hem de olası hata mesajlarını kullanacağız.
- using bloklarından içteki blokta ana mesajla hata mesajını bi değişkene alıyoruz. Bu arada python kodunda try-catch blokları var, ordaki hata değerlerini de ana çıktının bir parçası olarak alıyorum, zira ordaki try-catch blokları for döngüleri içinde, böylece bir hisse senedinin kaydı bulunmaması gibi durumlarda oluşan hatalar nedeniyle program tamamaen durmuyor, print mesajı ile çıktılar console'a yazdırılıyor. İşte biz stadard output ile bu çıktıları yakalıyoruz. Standard error ise program hata verip durduğunda çıkan hata mesajını yakalıyor.
Benzer bir çalışmayı siz bildiğiniz başka diller için de deneyebilirsiniz.
Bu arada bu çalışmayı komple c#'ta yapmak isteseydik bunun için de HtmlAgility isimli efsane bir paket var, nuget'tan bunu da indirebilirsiniz. Tabi bu aslında Python'daki BeautifulSoup'un muadili oluyor, investpy'nin değil. Zaten bu yüzden c# yerine python kullandık. Çünkü hazır bir API olduğu için python'la ilerlemek çok daha makul oldu.
Bu ikinci örneğimizde ise, diğer programa sadece metin parametre göndermekle kalmayacağız, aynı zamanda structered bir data da göndereceğiz. İşte burada karşımıza meşhur json yapıları ve serialization/deserialization kavramları çıkıyor.
Örneğimizin konusu şu: iki metnin birbirine ne oranda benzeştiğine bakıp birbirinin aynı olma ihtimaline bakacağız. Amacımız kurumumuzdaki veri yönetişimi faaliyetlerinden biri olan iş sözlüğümüzü oluşturmak. Bunu oluştururken de sözlüğe duplike(mükerrer) terimlerin girilmesini engellemek. Ancak bazen sözlüğe girilen terimler aslında aynı terim olduğu halde yazılışları farklı olabilmekte(ÖR: KK'lı müşteri adedi ve KK müşteri adedi), o yüzden klasik duplike bulma yöntemleri işimize yaramıyor. Bunun için yakın eşleşme(fuzzy match) yapmayı sağlayan kütüphaneler var. Bunun için Python'da fuzzywuzzy kütüphanesini kullanıyoruz ancak bunun kurulumu biraz alengirli, o yüzden ben basit olması adına difflib kütüphanesini kullanacağım. Zaten bu detaylar şuan sizin için önemli dğeil, siz .Net kısmına odaklanın.
Bunu yaparken de elimizde bi metin listesi olacak. Bu listeyi, kendisiyle karşılaştıracağız. Karşılaştırma işlemini pythona yaptıracağız. Bu karşılaştırmayı yaptırırken bazı zıt kelimelere bakmasın isteyeceğiz. Ör:"KK limiti azalan müşteri adedi" ve "KK limiti artan müşteri adedi" terimlerini bizi boşuna göstermesin, zira çıkan listede bunlar büyük oranda benziyor görünecek, bizi gereksiz meşgul etmiş olacak. Bunlar için bir istisna kelimeler listesine ihtiyacımız olacak.
Json işlemlerini yapabilmemiz için nuget'tan Newtonsoft.Json kütüphanesini indiriyoruz.
Aşağıdaki gibi bir formumuz olacak.
Terim listesi dosyasının içeriği de şöyle:
İstisna kontrolü sayesinde 4. ve 5.satırdaki ile 8. ve 9.satırdakiler için karşılaştırma skoru görmeyeceğiz.
Kodlarımıza bakalım:
Az önce belirttiğim gibi, burda ilaveten bir dictionary'yi json objesie haline dönüştürerek python'a gönderiyoruz, python da gelen bu datayı alıp kendi dictionary formatına çevirecek. Bu arada tabiki istersek pythondan da bir veri yapısını json olarak c#'a alıp, onu deserialize ederek bir .Net objesine(Dictioonary de olabilir başka bir yapı da) döndürebiliriz. Bu örnekte sadece biz python'a gönderimde bulunacağız, python'dan birşey almayacağız.
public partial class frmFuzzy : Form
{
Dictionary dict = new Dictionary();
public frmFuzzy()
{
InitializeComponent();
}
private void frmFuzzy_Load(object sender, EventArgs e)
{
dict.Add("artan", "azalan");
dict.Add("tl", "yp");
dict.Add("aktif", "inaktif");
var result = from d in dict
select new { d.Key, d.Value };
this.dataGridView1.DataSource = result.ToList();//kullanıcıya istisna listesi içeriği hakkında bilgi veriyoruz, istenirse buradan yeni key-value ikililieri de girilecek şekilde ayarlanabilir
}
private void button1_Click(object sender, EventArgs e)
{
string pyfile = @"E:\OneDrive\Dökümanlar\GitHub\dotnet\Ugulamalar\InvestPY\vstofuzzy.py";
string istisnaJson = JsonConvert.SerializeObject(dict).Replace("\"","\\\"");//Dicitionar>Json dönşüm işlemi burada. Jsonda özel anlamı olan " işaretlerini \" şeklinde gönderiyoruz ki bunları gerçek " gibi algılasın
Process process;
ProcessStartInfo start = new ProcessStartInfo(this.txtPythonexe.Text); //Filename propertysi yerine direkt yaratım sırasında da parametre verebiliyoruz
start.Arguments = string.Format("{0} \"{1}\" \"{2}\" {3} \"{4}\" {5}", pyfile, this.txtSource.Text, this.txtKolon.Text, this.txtEsik.Text, this.txtTarget.Text, istisnaJson); //source, kolon ve target kolonlarında boşluk olabilir diye ilave tırnak ekliyoruz
start.UseShellExecute = false;
start.CreateNoWindow = true;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true;
using (process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string stderr = process.StandardError.ReadToEnd();
string result = reader.ReadToEnd();
if (string.IsNullOrEmpty(stderr))
MessageBox.Show(string.Format("Sonuç:{0}", result));
else
{
MessageBox.Show(string.Format("Sonuç:{0}", stderr));
}
}
}
}
}
Python tarafında bu parametreleri aşağıdaki gibi karşılılyoruz
sourceworkbook=sys.argv[1] kolon=sys.argv[2] esik=int(sys.argv[3]) targetfile=sys.argv[4] istisnaJson=sys.argv[5]
Kod çalıştırılınca sonuç şöyledir:
Beklediğimiz sonuçları aldık.
Böylece bir konunun daha sonuna glemiş olduk.
Bu arada, bu kodun daha gelişmiş halini(eşanlam kontrolü, kelime köklerini alarak kontrol etme v.s gibi kontrollerin de olduğu) tamamen c# içinde kalacak şekilde hiç pythona bulaşmadan da yaptım. Onu şurada bulabilirsiniz.