Çoklu Monitör Kullanımında Ekranı Farklı Monitörde Açma

Çalışmakta olduğum bir projede kullanıcılar ekrandan tıklama yaparak yeni bir pencere açmakta ve açılan penceredeki bilgiler doğrultusunda ana pencerede işlem yapmaktadırlar. Ancak çift monitör ile çalışan kullanıcılar yeni pencerenin ana pencere ile aynı monitörde açılması nedeniyle her defasında yeni pencereyi sürükleyerek diğer monitöre aktarmakta ve bu da zaman kaybına yol açmaktadır. Bu nedenle açılacak olan yeni pencerenin ana pencerenin bulunduğu monitörden farklı diğer moniterde açılması için düzenleme yapma kararı aldık.

Bu noktada ilk akla gelen çözüm sunucu tarafında yeni bir process ile pencere açmak ve SetWindowPos metodu ile açılan pencereyi ilgili ekrana taşımaktır. Temel çalışma mantığı aşağıdaki gibi olacaktır.

ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
startInfo.Arguments = url;
Process newProcess = System.Diagnostics.Process.Start(startInfo);
IntPtr newProcessPtr = newProcess.MainWindowHandle;
SetWindowPos(newProcessPtr, IntPtr.Zero, x, y, cx, cy, SetWindowPosFlags.ShowWindow);

SetWindowPos metodu pencerenin pozisyonunu x ve y parametreleri ile almaktadır. cx ve cy ise pencere boyutlarını belirlemektedir. Bu noktada ana pencerenin hangi ekranda olduğunun bilinmesi ve yeni açılacak pencerenin de buna göre hangi pozisyonda açılacağının hesaplanması gerekmektedir.

Ekran bilgilerini almak için System.Windows.Forms kütüphanesi içinde Screen sınıfı bulunmaktadır. Bu sınıfın AllScreens adlı özelliği bilgisayara bağlı tüm ekranları yine Screen sınıfından bir dizi ile vermektedir. Screen sınıfının sınır koordinatlarından haraketle ekranları aşağıdaki gibi sıraya dizmek mümkündür.

List<KeyValuePair<int, int>> screenList = new List<KeyValuePair<int, int>>();
for (int i = 0; i < Screen.AllScreens.Length; i++)
      screenList.Add(new KeyValuePair<int, int>(i, Screen.AllScreens[i].Bounds.X));
screenList.Sort((firstPair, nextPair) =>
{
      return firstPair.Value.CompareTo(nextPair.Value);
});

Javascript tarafında ana pencerenin bulunduğu pozisyon bilgisini window.screenLeft ile alıp sunucu tarafına aktarabiliriz. Bu değer üzerinden sıralanmış ekranlar içinde açılacak olan yeni pencerenin ekranını belirlemek mümkün olacaktır.

int targetScreenId = 0;
foreach (KeyValuePair<int, int> screen in screenList)
{
      if (screen.Value > screenLeft)
          targetScreenId = screen.Key;
}

Açılacak yeni pencerenin konumu belirlendikten sonra değerleri SetWindowPos metoduna aktarılabilir. Burada pencere boyutları için ekran boyutlarını geçerek pencerenin tam ekran olarak açılmasınını sağlıyoruz.

SetWindowPos(newProcessPtr, IntPtr.Zero,
Screen.AllScreens[targetScreenId].WorkingArea.Left, 
Screen.AllScreens[targetScreenId].WorkingArea.Top, 
Screen.AllScreens[targetScreenId].Bounds.Size.Width, 
Screen.AllScreens[targetScreenId].Bounds.Size.Height, 
SetWindowPosFlags.ShowWindow);

1
2

Bu hali ile kodumuzu çalıştırdığımızda process üzerinden MainWindowHandle ulaştığımız anda “Process has exited, so the requested information is not available.” şeklinde istisna fırlatıldığını görürüz. Bu durum aşağıdaki sayfada açıklanmış ve çözümü verilmiştir.

http://stackoverflow.com/questions/1825105/process-startiexplore-exe-immediately-fires-the-exited-event-after-launch

Most probably is that you have IE already running as a process, so when you try to launch it again as a new process it looks that there are IE running already, tells it that user initiated a new window (so the initial IE will create a “new” window rather than a new one) and exit. Possible solution: try starting the process with “-nomerge” command line option.

“-nomerge” komutu IE8 öncesi tarayıcılarda kullanılmakta olup IE8 ve sonrası için bu komut yerine “-noframemerging” komutu kullanılmaktadır. Detaylı bilgi için aşağıdaki bağlantıyı inceleyebilirsiniz.

https://msdn.microsoft.com/en-us/library/hh826025(v=vs.85).aspx

ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
startInfo.Arguments = "-noframemerging " + url;
Process newProcess = System.Diagnostics.Process.Start(startInfo);

Bu hali ile kodumuzu çalıştırdığımızda istediğimizin gerçekleşmediğini görürüz. Debug ettiğimizde ise newProcessPtr değerinin 0 olduğu ortaya çıkar. Bu durum aşağıdaki sayfada açıklanmış ve çözümü verilmiştir.

https://social.msdn.microsoft.com/Forums/vstudio/en-US/021c76c6-1a66-4b96-8ce7-89c2a12f4bcb/procstart-procwaitforinputidle-setwindowpos-does-not-work?forum=csharpgeneral

The reason for SetWindowPos method execution failed is that the Process. MainWindowHandle is not acquired in time, it is still 0 when SetWindowPos was executed.As BinaryCoder suggested, all we need to do is add a loop to check the handle status, we all in the loop until the handle was obtained.

int count = 0;
while (newProcess.MainWindowHandle == IntPtr.Zero && count < 2000)
{
     Thread.Sleep(20);
     count += 20;
}

IntPtr newProcessPtr = newProcess.MainWindowHandle;

Yukarıdaki durumu araştırırken aşağıdaki gibi bir bilgi ile karşılaştım. Bu nedenle döngü içine newProcess.Refresh(); eklemenin doğru olacağını düşünüyorum.

The main window is the window opened by the process that currently has the focus (the TopLevel form). You must use the Refresh method to refresh the Process object to get the current main window handle if it has changed.

Sonuç olarak birden fazla monitör kullanan kullanıcılarda açılacak olan yeni pencerenin ana pencerenin bulunduğu monitörden farklı bir moniterde açılmasını sağlayan kodumuz aşağıdaki gibidir.

if (Screen.AllScreens.Length > 1)
{
        ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
        startInfo.WindowStyle = ProcessWindowStyle.Maximized;
        startInfo.Arguments = "-noframemerging " + url;

        Process newProcess = System.Diagnostics.Process.Start(startInfo);

        if (newProcess.Id >= 0)
        {
               int count = 0;
               while (newProcess.MainWindowHandle == IntPtr.Zero && count < 2000)
               {
                      Thread.Sleep(20);
                      newProcess.Refresh();
                      count += 20;
               }
               IntPtr newProcessPtr = newProcess.MainWindowHandle;

               List<KeyValuePair<int, int>> screenList = new List<KeyValuePair<int, int>>();

               for (int i = 0; i < Screen.AllScreens.Length; i++)
                    screenList.Add(new KeyValuePair<int, int>(i, Screen.AllScreens[i].Bounds.X));

               screenList.Sort((firstPair, nextPair) =>
               {
                    return firstPair.Value.CompareTo(nextPair.Value);
               });

               int targetScreenId = 0;
               foreach (KeyValuePair<int, int> screen in screenList)
               {
                    if (screen.Value > screenLeft)
                           targetScreenId = screen.Key;
               }

               SetWindowPos(newProcessPtr, IntPtr.Zero, Screen.AllScreens[targetScreenId].WorkingArea.Left, Screen.AllScreens[targetScreenId].WorkingArea.Top,             Screen.AllScreens[targetScreenId].Bounds.Size.Width, Screen.AllScreens[targetScreenId].Bounds.Size.Height, Win32.SetWindowPosFlags.ShowWindow);

       }
}
Reklamlar

Enum Flag Özelliği

Enum, programlamada oldukça sık başvurulan, sayısal değerleri anlamlı bir şekilde isimlendirilerek kullanılabilmesini sağlayan yapıdır. Enum yapısı yaygın olarak her bir üyesinin tek bir değer taşıdığı hali ile kullanılır. Örneğin bir ev konum itibariyle tek bir şehirde yer alır. Bu durumda evin bulunduğu şehri bir enum yapısının tek bir üyesi ile aşağıdaki gibi gösterebiliriz:

public enum Sehirler {
   Ankara,
   Antalya,
   Istanbul,
   Kocaeli
}
ev.Konumu = Sehirler.Ankara;

Ancak bazen tek bir üye ifade için yeterli olmaz. Örneğin bir evin sahip olabileceği özellikler enum üyeleri ile temsil edildiğinde birden fazla özellik aynı evde bulunabilir. Bu durumda evin sahip olduğu özellikleri nasıl göstereceğimiz sorusu gündeme gelir.

public enum Ozellikler {
   Balkonlu,
   Klimali,
   Panjurlu,
   Otoparkli
}

Bu problemi çözmek için akla iki yöntem gelmektedir:

  • Olası tüm kombinasyonlar enum içinde üye olarak eklenir.
    public enum Ozellikler {
      Balkonlu,
      Klimali,
      Panjurlu,
      Otoparkli,
      BalkonluKlimali,
      BalkonluPanjurlu,
      BalkonluOtoparkli,
      KlimaliPanjurlu,
      KlimaliOtoparkli,
      PanjurluOtoparkli,
      BalkonluKlimaliPanjurlu,
      BalkonluKlimaliOtoparkli,
      BalkonluPanjurluOtoparkli,
      KlimaliPanjurluOtoparkli,
      BalkonluKlimaliPanjurluOtoparkli
    }
    
  • Evin sahip olduğu özellikler liste olarak tutulur
    ev.Ozellikleri = { Ozellikler.Balkonlu, Ozellikler.Klimali }
    

Birinci yöntem az sayıda özellik için kullanılabilir olsa da özellik sayısının fazla olması durumunda ortaya çok sayıda kombinasyon çıkacağından kullanılması zordur. İkicinci yöntem çok daha mantıklı gözükse de verilerin saklanması noktasında veritabanında fazladan tablo oluşturulması ve verinin okunup yazılması noktasında fazladan kodlama gerektirecektir.

Enum yapılarında kullanılan Flag özelliğine tam da bu noktada ihtiyaç duyulmaktadır. Flag özelliği arka planda enum üyeleri üzerinde mantıksal işlemler yaparak bize çoklu değerler kullanma imkanı verir. Flag özelliğinin kullanımı aşağıdaki gibidir:

[Flag]
public enum Ozellikler {
   Balkonlu = 1, 	// 2^0
   Klimali = 2, 	// 2^1
   Panjurlu = 4, 	// 2^2
   Otoparkli = 8 	// 2^3
}

Flag özelliğinin kullanımında dikkat edilecek olan nokta enum üye değerlerinin 1, 2, 4, 8 … şeklinde 2’nin üsleri olacak şekilde atanmasıdır. Burada amaç ikili düzende yapılacak olan mantıksal işlemlerle tüm kombinasyonları elde etmektir.

Bir enum yapısındaki üyeler ile elde edilebilecek tüm kombinasyonların sayısı n üye sayısı olmak üzere 2n-1’dir. (Hiçbir üyenin dahil olmadığı seçeneği yani boş kümeyi de düşünürsek sayı 2n olur.) 2n adet seçeneği ikilik düzende n basamak ile ifade edebiliriz.

Üye 10’luk Düzen 2’lik Düzen
Boş küme 0 0000
Balkonlu 1 0001
Klimali 2 0010
BalkonluKlimali 3 0011
Panjurlu 4 0100
BalkonluPanjurlu 5 0101
KlimaliPanjurlu 6 0110
BalkonluKlimaliPanjurlu 7 0111
Otoparkli 8 1000
BalkonluOtoparkli 9 1001
KlimaliOtoparkli 10 1010
BalkonluKlimaliOtoparkli 11 1011
PanjurluOtoparkli 12 1100
BalkonluPanjurluOtoparkli 13 1101
KlimaliPanjurluOtoparkli 14 1110
BalkonluKlimaliPanjurluOtoparkli 15 1111

Flag özelliğinin kodlama içerisinde kullanımı ise oldukça basittir. Değişkene atanmak istenen enum üyeleri mantıksal OR işlemine sokulur. Böylece enum değişkeni üzerinde istenen kombinasyonun saklanması sağlanır.

ev.Ozellikleri = Ozellikler.Balkonlu | Ozellikler.Otoparkli;

Elimizdeki enum değişkeninin belirli bir enum üyesini ya da belirli bir kombinasyonu barındırıp barındırmadığını belirlemek için HasFlag metodu kullanılır. HasFlag metodu değişken içerisinde, parametre olarak verdiğimiz enum üye(ler)ini barındırıyorsa geriye true değerini döner.

ev.Ozellikleri = Ozellikler.Balkonlu |
                 Ozellikler.Otoparkli |
                 Ozellikler.Klimali;
ev.Ozellikleri.HasFlag(Ozellikler.Klimali);	//true
ev.Ozellikleri.HasFlag(Ozellikler.Panjurlu);	//false
ev.Ozellikleri.HasFlag(Ozellikler.Balkonlu | Ozellikler.Otoparkli); //true
ev.Ozellikleri.HasFlag(Ozellikler.Balkonlu | Ozellikler.Panjurlu);  //false

Flag özelliğinin en güzel yanlarından birisi de ToString() metodu ile enum değişkeni içerisindeki tüm enum üyelerinin CVS formatında virgülle ayrılarak yazılıyor olmasıdır.

ev.Ozellikleri = Ozellikler.Balkonlu |
                 Ozellikler.Otoparkli |
                 Ozellikler.Klimali;
ev.Ozellikleri.ToString();	// Bankonlu, Otoparkli, Klimali

Bunun yanında enum tipi içerisinde bulunan Parse metodu içerisinde çoklu enum isimlerini virgül ile ayırarak verildiğinde ilgili değerlerin otomatik olarak atanması mümkündür.

ev.Ozellikleri = (Ozellikler)Enum.Parse(typeof(Ozellikler),
                                        "Bankonlu, Otoparkli");

Enum için izin verilen veri tipleri byte, sbyte, short, ushort, int, uint, long ve ulong’dur. Bu veri tiplerinden ulong kullanılması durumunda enum içinde maksimum 64 değer üye tanımlanabilir.

public enum WebAgentPermission : ulong
{
	RuleGroup = 1,						//2^0
	Location = 2,						//2^1
	VolumeStatistics = 4,				//2^2
	...
	UserPassword = 256,					//2^8
	...
	AuditLogs = 65536,					//2^16
              ...
	WebAgentReports = 4294967296,				//2^32
	...
	PespectiveSearch =  281474976710656,		//2^48
	...
	DictionaryManager = 9223372036854775808         //2^63
}

photo