C# ile Ekran Görüntüsü (Screenshot) Alma
- 19
- (1)
- (5)
- 24 Ağu 2024
Proje Oluşturma
C# ile ekran görüntüsü almak için bir Visual Studio 2022 ile .NET Framework WPF projesi oluşturulacaktır. Dilerseniz Console veya Windows Forms projesi de oluşturup ekran görüntüsü alma işlemini orada gerçekleştirebilirsiniz.
Proje için ScreenShotWPF adını kullanabiliriz. .NET Framework 8.0
sürümü seçilerek oluşturulacaktır.
Proje oluşturulduktan sonra karşınıza WPF tasarım penceresi ve XAML
formatı gelecektir.
XAML formatı düzenlenerek forma istenilen basit bir şekil verilebilir.
<Window x:Class="ScreenShotWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenShotWPF"
mc:Ignorable="d"
Title="Ekran Görüntüsü Alma" Height="480" Width="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="1*" />
<RowDefinition Height="32" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="Görüntü Al" Width="100" Height="24" Margin="4" Click="TakeScreenShot_Clicked" />
</StackPanel>
<ListBox x:Name="lbFiles" Grid.Row="1">
</ListBox>
<TextBlock Grid.Row="2" x:Name="tbStatus" Text="" Height="24" Margin="4" />
</Grid>
</Window>
Click="TakeScreenShot_Clicked"
şeklinde Görüntü Al butonunun Click
eventine bağlanmış bir metot olması gereklidir. TakeScreenShot_Clicked
yazısı üzerinde gelip F12
tuşuna basarsanız, WPF penceresinin kod kısmında bu metot otomatik oluşturulacaktır.
WPF penceresinin görünümü yukarıdaki XAML formatını kullandıktan sonra aşağıdaki gibi olacaktır. En üst satır butonlar için 32px yüksekliğinde, orta kısım alınan ekran görüntülerini ListBox
içerisinde dosya browserı şeklinde görüntülemek içindir. En alttaki TextBlock
içerisinde de görüntü alındıktan sonra bildirim yazı halinde gösterilebilir.
Görünümü WPF'in farklı özelliklerini kullanarak stilize edebilmeniz mümkündür.
Proje içerisinde System.Windows.Bitmap
sınıfı kullanılacağı için proje özelliklerine gidip Windows Forms ayarının etkinleştirilmesi gerekmektedir. Daha eski .NET Framework projelerinde References
içerisinde kütüphane olarak eklenebiliyorken, .NET 8.0
projesi için bu mümkün değildir.
Solution Explorer üzerinden proje adına sağ klik tıklayıp, Properties ile ayarlara gidin.
Enable Windows Forms for this project. ayarını proje için etkinleştirin.
Click="TakeScreenShot_Clicked"
şeklinde Görüntü Al butonunun Click
eventine bağlanmış bir metot olması gereklidir. TakeScreenShot_Clicked
yazısı üzerinde gelip F12
tuşuna basarsanız, WPF penceresinin kod kısmında bu metot otomatik oluşturulacaktır. Bu metodu oluşturduktan sonra arkaplan kodu şu şekilde olur.
namespace ScreenShotWPF
{
public partial class MainWindow : Window
{
private void TakeScreenShot_Clicked(object sender, RoutedEventArgs e)
{
}
}
}
Ekran görüntüsü almak için Windows'un sistem DLL dosyalarına bağlanılacaktır. Bunun için using System.Runtime.InteropServices;
kodu kütüphane kullanımı en üste eklenmelidir.
MainWindow
sınıfının içerisinde en üst kısma aşağıdaki DLL dosyalarından çekilecek sistem metotları eklenmelidir. SRCCOPY
ve CAPTUREBLT
isimli iki adet int
değişken tanımlanmalıdır.
namespace ScreenShotWPF
{
public partial class MainWindow : Window
{
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int nxDest, int nyDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern uint GetDpiForWindow(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
const int SRCCOPY = 0x00CC0020;
const int CAPTUREBLT = 0x40000000;
private void TakeScreenShot_Clicked(object sender, RoutedEventArgs e)
{
}
}
}
Aşağıdaki metot ile ekrandaki bir dikdörtgenin görüntüsü alınabilmektedir. Metodun geri dönüşü Bitmap
türünde olacaktır. Bu veri türünü direkt dosya olarak kaydedebilirsiniz. Veya byte
dizisine çevirip herhangi bir veritabanına da ekleyebilirsiniz.
private Bitmap CaptureRegion(Rectangle region)
{
IntPtr desktophWnd;
IntPtr desktopDc;
IntPtr memoryDc;
IntPtr bitmap;
IntPtr oldBitmap;
uint dpi;
bool success;
Bitmap result;
desktophWnd = GetDesktopWindow();
dpi = GetDpiForWindow(desktophWnd);
desktopDc = GetWindowDC(desktophWnd);
memoryDc = CreateCompatibleDC(desktopDc);
bitmap = CreateCompatibleBitmap(desktopDc, region.Width, region.Height);
oldBitmap = SelectObject(memoryDc, bitmap);
success = BitBlt(memoryDc, 0, 0, region.Width, region.Height, desktopDc, region.Left, region.Top, SRCCOPY | CAPTUREBLT);
try
{
if (!success) { throw new Win32Exception(); }
result = System.Drawing.Image.FromHbitmap(bitmap);
}
finally
{
SelectObject(memoryDc, oldBitmap);
DeleteObject(bitmap);
DeleteDC(memoryDc);
ReleaseDC(desktophWnd, desktopDc);
}
return result;
}
Eğer birden fazla monitör kullanılıyorsa System.Windows.Forms.Screen
veri türü ile aktif monitörleri dizi halinde alarak, görüntüleri birleşik halde kaydedebilirsiniz. Aşağıdaki metot, yukarıdaki CaptureRegion
metodunu kullanarak bütün monitörlerin birleşik görüntüsünü almaktadır.
private Bitmap CaptureDesktop()
{
Rectangle desktop;
Screen[] screens; // Windows.Forms.Screen türünde aktif ekranlar
desktop = Rectangle.Empty;
screens = Screen.AllScreens; //Aktif ekranları dizi olarak alır
for (int i = 0; i < screens.Length; i++)
{
Screen screen;
screen = screens[i];
// Rectangle.Union metodu ile alınan her bir ekranın dikdörtgeni birleştirilir.
desktop = Rectangle.Union(desktop, screen.Bounds);
}
// Alınan nihayi görüntü result olacaktır
Bitmap result = CaptureRegion(desktop);
return result;
}
Son olarak nihayi Bitmap
türünü dosya olarak, programın bulunduğu konumdaki screenshot klasörüne kaydeden bir metot yazılabilir. Aşağıdaki metot Bitmap
nesnesini o dizinin içerisine kaydedecektir. Eğer screenshot dizini mevcut değilse oluşturacaktır.
private void SaveScreenShot(Bitmap bitmap)
{
// Eğer screenshot dizini mevcut değilse
if (!System.IO.Directory.Exists("screenshots"))
{
// screenshot dizinini oluştur
System.IO.Directory.CreateDirectory("screenshots");
}
// screenshot dizini ile ekran görüntüsünü yıl.ay.gün saat.dakika.saniye.salise formatı ile kaydet
bitmap.Save(System.IO.Path.Combine("screenshots", DateTime.Now.ToString("yyyy.MM.dd HH.mm.ss.FFF") + ".png"));
}
SaveScreenShot
metodunu, CaptureDesktop
metodu içerisinde kullanarak alınan görüntü dosya halinde kaydedilebilir. Son olarak arkadaki kod tamamı ile şu şekilde olacaktır.
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
namespace ScreenShotWPF
{
public partial class MainWindow : Window
{
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int nxDest, int nyDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern uint GetDpiForWindow(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
const int SRCCOPY = 0x00CC0020;
const int CAPTUREBLT = 0x40000000;
public MainWindow()
{
InitializeComponent();
}
private Bitmap CaptureRegion(Rectangle region)
{
IntPtr desktophWnd;
IntPtr desktopDc;
IntPtr memoryDc;
IntPtr bitmap;
IntPtr oldBitmap;
uint dpi;
bool success;
Bitmap result;
desktophWnd = GetDesktopWindow();
dpi = GetDpiForWindow(desktophWnd);
desktopDc = GetWindowDC(desktophWnd);
memoryDc = CreateCompatibleDC(desktopDc);
bitmap = CreateCompatibleBitmap(desktopDc, region.Width, region.Height);
oldBitmap = SelectObject(memoryDc, bitmap);
success = BitBlt(memoryDc, 0, 0, region.Width, region.Height, desktopDc, region.Left, region.Top, SRCCOPY | CAPTUREBLT);
try
{
if (!success) { throw new Win32Exception(); }
result = System.Drawing.Image.FromHbitmap(bitmap);
}
finally
{
SelectObject(memoryDc, oldBitmap);
DeleteObject(bitmap);
DeleteDC(memoryDc);
ReleaseDC(desktophWnd, desktopDc);
}
return result;
}
private Bitmap CaptureDesktop()
{
Rectangle desktop;
Screen[] screens; // Windows.Forms.Screen türünde aktif ekranlar
desktop = Rectangle.Empty;
screens = Screen.AllScreens; //Aktif ekranları dizi olarak alır
for (int i = 0; i < screens.Length; i++)
{
Screen screen;
screen = screens[i];
// Rectangle.Union metodu ile alınan her bir ekranın dikdörtgeni birleştirilir.
desktop = Rectangle.Union(desktop, screen.Bounds);
}
Bitmap result = CaptureRegion(desktop);
SaveScreenShot(result);
return result;
}
private void SaveScreenShot(Bitmap bitmap)
{
if (!System.IO.Directory.Exists("screenshots"))
{
System.IO.Directory.CreateDirectory("screenshots");
}
bitmap.Save(System.IO.Path.Combine("screenshots", DateTime.Now.ToString("yyyy.MM.dd HH.mm.ss.FFF") + ".png"));
}
private void TakeScreenShot_Clicked(object sender, RoutedEventArgs e)
{
CaptureDesktop();
}
}
}
Alınan Ekran Görüntülerini ListBox İçinde Gösterme
Ekran görüntülerinin dosyalarını ListBox
içerisinde göstermek için Binding
yöntemi uygulanabilir. Binding, arkaplandaki verileri bir veri türü ile arayüze bağlayıp, her değişiklik olduğunda arayüzün güncellenmesidir.
Veri türü olarak proje içerisinde yeni bir class
oluşturulabilir. İçerisinde sadece dosya yolu ve dosya adı olan class
veri türünün adı ScreenShots olsun. ScreenShots.cs dosyası oluştuktan sonra içeriği şu şekilde olacaktır.
using System;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ScreenShotWPF
{
public class ScreenShots
{
// ObservableCollection yerine normal Collection kullanılırsa arayüz güncellenmeyecektir
public ObservableCollection<ScreenShot> Files { get; set; }
public ScreenShots()
{
Files = new ObservableCollection<ScreenShot>();
}
public void RenderFiles()
{
Files.Clear();
// screenshots klasörü DirectoryInfo olarak tanımlanır
System.IO.DirectoryInfo directory = new System.IO.DirectoryInfo("screenshots");
// Eğer klasör mevcut değilse return ile metot bitirilir
if (!directory.Exists) { return; }
// Klasördeki dosyalar alınır
var files = directory.GetFiles();
// Dosyalar döngü ile Files koleksiyonuna ScreenShot türünde eklenir
foreach (var file in files)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(file.FullName, UriKind.Absolute);
image.EndInit();
// WPF Image nesnesinin Source özelliği ImageSource türündedir
ImageSource source = image;
// Dosya adındaki .png uzantıları silinirek eklenir
Files.Add(new ScreenShot() { Name = file.Name.Replace(".png", ""), Image = image });
}
}
}
public class ScreenShot
{
public ScreenShot() { }
public string Name { get; set; }
public BitmapImage Image { get; set; }
}
}
MainWindow.xaml.cs içerisinde ScreenShots
türünde bir öğe tanımlanır. MainWindow
ilk oluşturulduğunda ilk değerler verilir.
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
namespace ScreenShotWPF
{
public partial class MainWindow : Window
{
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int nxDest, int nyDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern uint GetDpiForWindow(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
const int SRCCOPY = 0x00CC0020;
const int CAPTUREBLT = 0x40000000;
private ScreenShots screenShots;
public MainWindow()
{
InitializeComponent();
screenShots = new ScreenShots();
// ListBox kontrolünün adı lbFiles olarak belirlenmişti
// DataContext özelliğine Binding ile alınacak veri atanır
lbFiles.DataContext = screenShots;
screenShots.RenderFiles();
System.Windows.Controls.Image img = new System.Windows.Controls.Image();
}
private Bitmap CaptureRegion(Rectangle region)
{
IntPtr desktophWnd;
IntPtr desktopDc;
IntPtr memoryDc;
IntPtr bitmap;
IntPtr oldBitmap;
uint dpi;
bool success;
Bitmap result;
desktophWnd = GetDesktopWindow();
dpi = GetDpiForWindow(desktophWnd);
desktopDc = GetWindowDC(desktophWnd);
memoryDc = CreateCompatibleDC(desktopDc);
bitmap = CreateCompatibleBitmap(desktopDc, region.Width, region.Height);
oldBitmap = SelectObject(memoryDc, bitmap);
success = BitBlt(memoryDc, 0, 0, region.Width, region.Height, desktopDc, region.Left, region.Top, SRCCOPY | CAPTUREBLT);
try
{
if (!success) { throw new Win32Exception(); }
result = System.Drawing.Image.FromHbitmap(bitmap);
}
finally
{
SelectObject(memoryDc, oldBitmap);
DeleteObject(bitmap);
DeleteDC(memoryDc);
ReleaseDC(desktophWnd, desktopDc);
}
return result;
}
private Bitmap CaptureDesktop()
{
Rectangle desktop;
Screen[] screens; // Windows.Forms.Screen türünde aktif ekranlar
desktop = Rectangle.Empty;
screens = Screen.AllScreens; //Aktif ekranları dizi olarak alır
for (int i = 0; i < screens.Length; i++)
{
Screen screen;
screen = screens[i];
// Rectangle.Union metodu ile alınan her bir ekranın dikdörtgeni birleştirilir.
desktop = Rectangle.Union(desktop, screen.Bounds);
}
Bitmap result = CaptureRegion(desktop);
SaveScreenShot(result);
return result;
}
private void SaveScreenShot(Bitmap bitmap)
{
if (!System.IO.Directory.Exists("screenshots"))
{
System.IO.Directory.CreateDirectory("screenshots");
}
bitmap.Save(System.IO.Path.Combine("screenshots", DateTime.Now.ToString("yyyy.MM.dd HH.mm.ss.FFF") + ".png"));
// Her görüntü alındığında liste (lbFiles) yenilenecektir
screenShots.RenderFiles();
}
private void TakeScreenShot_Clicked(object sender, RoutedEventArgs e)
{
CaptureDesktop();
}
}
}
lbFiles
kontrolünün DataContext
özelliğine atanan değerin her güncellendiğinde, lbFiles
isimli ListBox'un da güncellenebilmesi için XAML arayüzünde Binding tanımlanmalıdır. ListBox'a ItemsPanelTemplate
olarak WrapPanel
eklenirse, liste dosya görüntüleyen bir dosya gezgini görünümü alacaktır. XAML formatını aşağıdaki şekilde güncelleyebilirsiniz.
<Window x:Class="ScreenShotWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenShotWPF"
mc:Ignorable="d"
Title="Ekran Görüntüsü Alma" Height="480" Width="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="1*" />
<RowDefinition Height="32" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="Görüntü Al" Width="100" Height="24" Margin="4" Click="TakeScreenShot_Clicked" />
</StackPanel>
<ListBox x:Name="lbFiles" Grid.Row="1" ItemsSource="{Binding Files}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="120" Height="120">
<Image Source="{Binding Image}" Width="110" Height="100" />
<TextBlock Text="{Binding Name}" FontSize="10" Width="110" Height="20" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2" x:Name="tbStatus" Text="" Height="24" Margin="4" />
</Grid>
</Window>
Uygulamayı çalıştırdığınızda her görüntü alındığında liste dolacaktır.
Ekran görüntüsü alırken uygulamanın alınan görüntüye çıkmaması için, görüntü almadan 50ms önce pencereyi gizleyip, ardından tekrar gösterebilirsiniz. Bunun için TakeScreenShot_Clicked
metodunun içini şu şekilde güncelleyin.
private void TakeScreenShot_Clicked(object sender, RoutedEventArgs e)
{
this.Hide();
Thread.Sleep(50);
CaptureDesktop();
this.Show();
}