C# ile Ekran Görüntüsü (Screenshot) Alma

  • 51
  • (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.

wpf screenshot

Proje için ScreenShotWPF adını kullanabiliriz. .NET Framework 8.0 sürümü seçilerek oluşturulacaktır.

wpf screenshot

Proje oluşturulduktan sonra karşınıza WPF tasarım penceresi ve XAML formatı gelecektir.

wpf screenshot

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.

wpf screenshot

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.

wpf screenshot

Enable Windows Forms for this project. ayarını proje için etkinleştirin.

wpf screenshot

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.

wpf screenshot

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();
}

Projenin Visual Studio 2022 ile GitHub'a upload edilmiş halini buradan inceleyebilir ve indirebilirsiniz.

İlişkili İçerikler

Masaüstü uygulama geliştirmek için Windows Forms türüne alternatif olarak WPF türü uygulamalar geliştirebilirsiniz.

WPF içerisinde özelleştirilmiş UIElement nesneleri kullanmak için WPF Toolkit Extended kütüphanesini kullanabilirsiniz. Ancak bu içerikte bu kütüphaneye gerek duymadan Textbox nesnesine sadece sayı değeri girişinin nasıl yapıldığına değiniliyor.

WPF uygulaması içerisinde ListBox nesnesinin kullanım şekillerini bu içerikle öğrenebilir ve uygulayabilirsiniz.

Paylaşın
Etiket Bulutu