WPF

Posts referentes à plataforma WPF (Windows Presentation Foundation)

.Net: Classe para pesquisa de arquivos assíncrona e com múltiplos filtros – C#

Olá Pessoal,

Esse é o meu último dia de férias do trabalho (choro eterno), e acabei escrevendo um código para uma contribuição no MSDN que achei que seria interessante publicar.

É uma classe para pesquisa de arquivos assíncrona e que permite o uso de múltiplos filtros (de forma simultânea, na qual só serão retornados os resultados que satisfizerem a todos os filtros, de forma normal, onde serão retornados os arquivos que atendem a qualquer um dos filtros, mesmo que só um).

É um código simples, mas que na pressa, vale um Ctrl + C Ctrl + V. Enfim, segue o código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;

namespace HL.IO
{
    public class FileSearch
    {
        private string[] _Filters;
        public string[] Filters
        {
            get { return _Filters; }
            set { _Filters = value; }
        }

        private SearchOptions _SearchOption;
        public SearchOptions SearchOption
        {
            get { return _SearchOption; }
            set { _SearchOption = value; }
        }

        public FileSearch(SearchOptions SearchOption, params string[] Filters)
        {
            _Filters = Filters;
            _SearchOption = SearchOption;
        }
        public FileSearch()
        {
            _SearchOption = SearchOptions.AllDirectories | SearchOptions.MatchAllFilters;
            _Filters = null;
        }
        public IEnumerable<string> Search(string RootFolder)
        {
            if (_SearchOption.HasFlag(SearchOptions.AllDirectories))
            {
                IEnumerable<string> result = Directory.EnumerateFiles(RootFolder, "*.*", System.IO.SearchOption.AllDirectories);
                if (_SearchOption.HasFlag(SearchOptions.MatchAllFilters))
                    return result.Where(s => Path.GetFileName(s).ContainsAll(_Filters,
                        _SearchOption.HasFlag(SearchOptions.CaseSensitive)));
                else
                    return result.Where(s => Path.GetFileName(s).ContainsAny(_Filters,
                        _SearchOption.HasFlag(SearchOptions.CaseSensitive)));
            }
            else
            {
                IEnumerable<string> result = Directory.EnumerateFiles(RootFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly);
                if (_SearchOption.HasFlag(SearchOptions.MatchAllFilters))
                    return result.Where(s => Path.GetFileName(s).ContainsAll(_Filters,
                        _SearchOption.HasFlag(SearchOptions.CaseSensitive)));
                else
                    return result.Where(s => Path.GetFileName(s).ContainsAny(_Filters,
                        _SearchOption.HasFlag(SearchOptions.CaseSensitive)));
            }
        }

        #region Async
        public void AsyncSearch(string RootFolder)
        {
            SynchronizationContext context = SynchronizationContext.Current;
            if (context == null)
            {
                context = new System.Threading.SynchronizationContext();
            }
            Thread th = new Thread(new ThreadStart(() =>
            {
                IEnumerable<string> result = Search(RootFolder);
                context.Send(new SendOrPostCallback((object state) =>
                {
                    OnAsyncSearchCompleted((IEnumerable<string>)state);
                }), result);
            }));
            th.IsBackground = false;
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
        }
        public event AsyncSearchCompletedEventHandler AsyncSearchCompleted;
        protected virtual void OnAsyncSearchCompleted(IEnumerable<string> Result)
        {
            if (AsyncSearchCompleted != null)
            {
                AsyncSearchCompleted(this, new AsyncSearchCompletedEventArgs(Result));
            }
        }
        #endregion
    }

    public delegate void AsyncSearchCompletedEventHandler(object sender, AsyncSearchCompletedEventArgs e);

    public class AsyncSearchCompletedEventArgs
    {
        private IEnumerable<string> _result;
        public IEnumerable<string> Result
        {
            get { return _result; }
        }
        public AsyncSearchCompletedEventArgs(IEnumerable<string> Result)
        {
            _result = Result;
        }
    }
    public enum SearchOptions : int
    {
        /// <summary>
        /// The search engine will seach only on top level of the given root folder.
        /// </summary>
        TopDirectoryOnly = 1,
        /// <summary>
        /// The search engine will search in all the directory tree starting from the given root folder
        /// </summary>
        AllDirectories = 2,
        /// <summary>
        /// The search engine will use case sensitive approach.
        /// </summary>
        CaseSensitive = 4,
        /// <summary>
        /// If set, the search engine will return only the results matching all the filters.
        /// </summary>
        MatchAllFilters = 8
    };
    public static class StringExtensions
    {
        public static bool ContainsAll(this string Source, string[] strs, bool CaseSensitive)
        {
            foreach (string str in strs)
            {
                if (CaseSensitive)
                {
                    if (!Source.Contains(str)) return false;
                }
                else
                {
                    if (!Source.ToLower().Contains(str.ToLower())) return false;
                }
            }
            return true;
        }

        public static bool ContainsAny(this string Source, string[] strs, bool CaseSensitive)
        {
            foreach (string str in strs)
            {
                if (CaseSensitive)
                {
                    if (Source.Contains(str)) return true;
                }
                else
                {
                    if (Source.ToLower().Contains(str.ToLower())) return true;
                }
            }
            return false;
        }
    }
}

Mostrarei também um pequeno exemplo:

        private void btnSearch_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            dialog.ShowDialog();
            FileSearch searcher = new FileSearch(SearchOptions.AllDirectories, ".png", ".bmp", ".jpeg");
            searcher.AsyncSearchCompleted += searcher_AsyncSearchCompleted;
            searcher.AsyncSearch(dialog.SelectedPath);
        }

        private void searcher_AsyncSearchCompleted(object sender, AsyncSearchCompletedEventArgs e)
        {
            listBox1.Items.Clear();
            listBox1.Items.AddRange(e.Result.ToArray());
        }

No exemplo acima a classe FileSearch é usada para encontrar todos os arquivos PNG, BMP e JPEG da pasta informada. Isso é bem básico, mas dá para fazer bastante coisa. Incluir uma palavra chave que o nome do arquivo deve conter também é uma opção. E o melhor de tudo, assíncrono. Não irá congelar a interface de usuário enquanto realiza a busca.

That’s all folks!!!

WPF: Trabalhando com MDI no Windows Presentation Foundation

Olá pessoal,

Neste post irei apresentar algo interessante para a plataforma WPF. Trata-se de uma API, open-source, que permite a implementação de interfaces MDI, fáceis de usar no Windows Forms, mas que não estão disponíveis no Windows Presentation Foundation.

Essa libraria possui dois temas: Aero (Windows 7) e Luna (Windows XP).

Aero luna

Essa API está disponível de forma gratuita e open-source no Codeplex, através do link abaixo:

WPF Multiple Document Interface (MDI) – Home

Aproveitem!

C++ & C#(Wrapper): Definir/Pegar o Master Volume do Windows – Windows Vista+

Estava precisando desenvolver alguma forma para definir e pegar o volume da saída de som do Windows no C#/WPF. Obviamente, eu teria que lidar com a API do Windows.

Só que dessa vez, eu resolvi o problema de uma forma diferente. Pensei, a API do Windows foi escrita em C++, eu teria que importar um monte de interfaces, métodos, e estruturas para o C#. Então eu tive uma ideia melhor, escrever os métodos em uma DLL C++ e importa-los no C#. Foi exatamente o que eu fiz, ou quase. Na verdade o código em C++ eu encontrei na web, necessitando apenas uma pequena adaptação, no que acabou sendo meu primeiro projeto no C++. Depois criei o Wrapper (Api que faz a interoperabilidade entre a DLL C++, código nativo, e o .Net Framework, código gerenciado.

No fim das contas, ao invés de ter que importar um monte de interfaces, métodos, e estruturas para o C#, eu precisei importar apenas dois métodos. Aí eu vi vantagem em estudar C++, na verdade eu já vinha pensando em fazer esse tipo de coisa antes, mas nunca tive a real necessidade

Segue o código C++:

#include <mmdeviceapi.h>
#include <endpointvolume.h>
/*
The below code was based on: http://www.codeproject.com/Tips/233484/Change-Master-Volume-in-Visual-Cplusplus
Thanks to the author, Mr. Sayyed Mostafa Hashemi.
*/
extern "C" 
{
	_declspec(dllexport) bool WinVistaSetVolume(_In_ double nVolume, _In_ bool bScalar)
	{
		HRESULT hr = NULL;
		bool decibels = false;
		bool scalar = false;
		double newVolume = nVolume;

		CoInitialize(NULL);
		IMMDeviceEnumerator *deviceEnumerator = NULL;
		hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
			__uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
		IMMDevice *defaultDevice = NULL;

		hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
		deviceEnumerator->Release();
		deviceEnumerator = NULL;

		IAudioEndpointVolume *endpointVolume = NULL;
		hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume),
			CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
		defaultDevice->Release();
		defaultDevice = NULL;

		float currentVolume = 0;
		endpointVolume->GetMasterVolumeLevel(&currentVolume);


		hr = endpointVolume->GetMasterVolumeLevelScalar(&currentVolume);

		if (bScalar == false)
		{
			hr = endpointVolume->SetMasterVolumeLevel((float)newVolume, NULL);
		}
		else if (bScalar == true)
		{
			hr = endpointVolume->SetMasterVolumeLevelScalar((float)newVolume, NULL);
		}
		endpointVolume->Release();

		CoUninitialize();
		if (hr == 0x00000000)
			return true;
		else
			return false;
	}
	_declspec(dllexport) bool WinVistaGetVolume(_Out_ double& nVolume, _In_ bool bScalar)
	{
		HRESULT hr = NULL;
		CoInitialize(NULL);
		IMMDeviceEnumerator *deviceEnumerator = NULL;
		hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
			__uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
		IMMDevice *defaultDevice = NULL;

		hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
		deviceEnumerator->Release();
		deviceEnumerator = NULL;

		IAudioEndpointVolume *endpointVolume = NULL;
		hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume),
			CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
		defaultDevice->Release();
		defaultDevice = NULL;
		float currentVolume = 0;

		if (bScalar)
			hr = endpointVolume->GetMasterVolumeLevelScalar(&currentVolume);
		else
			hr = endpointVolume->GetMasterVolumeLevel(&currentVolume);
		endpointVolume->Release();

		CoUninitialize();

		nVolume = (double)currentVolume;
		
		if (hr == 0x00000000)
			return true;
		else
			return false;
	}
}

E o Wrapper em C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace HL.CSharp.SystemVolume
{
    public static class WinVista
    {
        public static double GetVolume()
        {
            double volume = 0;
            WinVistaGetVolume(ref volume, true);
            return volume;
        }
        public static bool SetVolume(double Volume)
        {
            return WinVistaSetVolume(Volume, true);
        }
        [DllImport("HL.CPP.SystemVolume.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool WinVistaSetVolume(double nVolume, bool bScalar);

        [DllImport("HL.CPP.SystemVolume.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool WinVistaGetVolume(ref double nVolume, bool bScalar);
    }
}

Vou disponibilizar também os arquivos do projeto e as DLLs já compiladas:

HL.CPP.SystemVolume

Espero que possa ser útil.

Icon Pro Beta: Download completo com código fonte e material de apoio

Fala galera!!

Meu projeto Icon Pro está quase completo.

Certeza que ainda tem bugs ocultos, e ainda não implementei todas as funcionalidades, mas o principal ele já faz, que é criar ícones a partir de imagens PNG/BMP.

O projeto foi desenvolvido no Visual Studio 2013:

Icon Pro Beta.zip – 4MB

Acompanha todo o material de apoio que eu usei para desenvolver a API, código fonte completo, e os executáveis compilados.

Espero que possa ser útil à vocês…

Icon Pro – Desenvolvimento em Andamento

Fala galera!

Na minha última postagem, estive falando sobre a possibilidade de eu criar um editor de ícones… Bom, a possibilidade está virando código! hehe

O conceito da aplicação já está definido, e as APIs de apoio estão 80% prontas (os 20% restantes são os Debugs e eventuais correções de bug..), faltando apenas definir o design da interface do aplicativo que vai ser desenvolvido no WPF (Framework 4.5).

Segue o diagrama geral do projeto:

Icon Pro Diagrama Programação

HL.IconPro.exe: O executável da aplicação, desenvolvido na plataforma WPF com design pattern MVVM.

HL.IconPro.Lib.Wpf.dll: A API de interoperabilidade para o WPF. Fornece a ponte entre os bytes e as imagens (BitmapFrames). As principais classes são a IconBitmapEncoder e IconBitmapDecoder.

HL.IconPro.Lib.Core.dll: A parte mais divertida está aqui! Manipulação byte-a-byte do arquivo de ícones. Aqui, as imagens são tratadas como buffers de bytes, e os ícones também. É nessa API que a calvície aumenta. Ela é multiplataforma (Portable Library). Se alguém, um dia, quiser abrir um arquivo de ícone no Windows Phone, fica mais facilzin..

Na verdade, eu separei a aplicação desta forma para praticar o conceito de aplicações multiplataformas, e também para facilitar o entendimento do código, e/ou correção de bugs.

Sendo o núcleo da aplicação uma Libraria Multiplataforma, ela não tem referência a nenhuma plataforma específica (WPF, Win Forms, etc).

Então, se alguém, quiser criar um editor de ícones no Win Forms, ou até no Windows Phone, essa pessoa só vai precisar recriar a Lib de Interoperabilidade para atender à plataforma desejada e a Interface da aplicação, com sua devida lógica é claro.

Até a próxima postagem pessoal!!!

WPF & Win Forms: Capturar um controle para uma imagem – C#

Fala galera,

As vezes, okay, muito raramente, aliás, eu nem mesmo tenho ideia de qual seja a utilidade disso, precisamos capturar um controle de nosso aplicativo, e “salva-lo” para uma Imagem.

Isso é possível, no Win Forms e de forma bem fácil no WPF.
Criei uma classe para fazer a captura no Win Forms, e outra classe para fazer a captura no WPF. A diferença? Você vê a seguir:

WPF:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace HerbertLausmann.Wpf
{
    class VisualToBitmap
    {
        public static System.Windows.Media.Imaging.BitmapSource Render(System.Windows.Media.Visual Visual, int Resolution = 96)
        {
            System.Windows.Media.Imaging.RenderTargetBitmap render;
            double width, height;
            int pixelWidth, pixelHeight;
            width = (double)Visual.GetValue(System.Windows.FrameworkElement.ActualWidthProperty);
            height = (double)Visual.GetValue(System.Windows.FrameworkElement.ActualHeightProperty);
            pixelWidth = Convert.ToInt32((width / 96) * Resolution);
            pixelHeight =  Convert.ToInt32((height / 96) * Resolution);
            render = new System.Windows.Media.Imaging.RenderTargetBitmap(
                pixelWidth, pixelHeight, Resolution, Resolution,
                System.Windows.Media.PixelFormats.Pbgra32);
            render.Render(Visual);
            return render;
        }
    }
}

Win Forms:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace HerbertLausmann.WinForms
{
    class HandleToBitmap
    {
        /// <summary>
        /// Gera um Bitmap diretamente do hWnd correspondente à Janela/Controle
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderHwnd(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(hWnd);
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetWindowRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(false), WndRect.Height(false));

            if (capBMP == IntPtr.Zero)
            {
                USER32.ReleaseDC(hWnd, hDC);
                GDI32.DeleteDC(capHDC);
                return null;
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(false), WndRect.Height(false), hDC, 0, 0, GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(hWnd, hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        /// <summary>
        /// A grosso modo, essa função tira um Print Screen da tela e recorta a imagem para as proporções da janela/controle.
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderFromDesktop(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(USER32.GetDesktopWindow());
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetWindowRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(), WndRect.Height());

            if (capBMP == IntPtr.Zero)
            {
                USER32.ReleaseDC(hWnd, hDC);
                GDI32.DeleteDC(capHDC);
                return null;
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(), WndRect.Height(), hDC, WndRect.X(), WndRect.Y(), GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(USER32.GetDesktopWindow(), hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        /// <summary>
        /// Gera um Bitmap correspondente à área cliente da Janela/Controle
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderClientArea(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(hWnd);
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetClientRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(false), WndRect.Height(false));

            if (capBMP == IntPtr.Zero)// if no compatible bitmap
            {
                USER32.ReleaseDC(hWnd, hDC); //   release window context
                GDI32.DeleteDC(capHDC); //   delete capture context
                return null; //   return null bitmap
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(false), WndRect.Height(false), hDC, WndRect.X(false), WndRect.Y(false), GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(hWnd, hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        private class GDI32
        {
            public const int SRCCOPY = 13369376;

            [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
            public static extern IntPtr DeleteDC(IntPtr hDc);

            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            public static extern IntPtr DeleteObject(IntPtr hDc);

            [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
            public static extern bool BitBlt(IntPtr hdcDest, int xDest,
                int yDest, int wDest, int hDest, IntPtr hdcSource,
                int xSrc, int ySrc, int RasterOp);

            [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc,
                int nWidth, int nHeight);

            [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

            [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
            public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);

            [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
            public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
        }
        private class USER32
        {
            public const int SM_CXSCREEN = 0;
            public const int SM_CYSCREEN = 1;

            [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
            public static extern IntPtr GetDesktopWindow();

            [DllImport("user32.dll", EntryPoint = "GetDC")]
            public static extern IntPtr GetDC(IntPtr ptr);

            [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
            public static extern int GetSystemMetrics(int abc);

            [DllImport("user32.dll", EntryPoint = "GetWindowDC")]
            public static extern IntPtr GetWindowDC(Int32 ptr);

            [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
            public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);

            [DllImport("user32.dll", EntryPoint = "GetWindowRect")]
            public static extern IntPtr GetWindowRect([In] IntPtr hWnd, out RECT lpRect);

            [DllImport("user32.dll", EntryPoint = "GetClientRect")]
            public static extern IntPtr GetClientRect([In] IntPtr hWnd, out RECT lpRect);

            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                int left;
                int top;
                int right;
                int bottom;

                public int X(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(left * getScalingFactor());
                    else
                        return left;
                }

                public int Y(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(top * getScalingFactor());
                    else
                        return top;
                }
                public int Width(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(Math.Abs(right - left) * getScalingFactor());
                    else
                        return (int)(Math.Abs(right - left));                    
                }

                public int Height(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(Math.Abs(bottom - top) * getScalingFactor());
                    else
                        return (int)(Math.Abs(bottom - top));
                }
            }

            [DllImport("gdi32.dll")]
            static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

            public enum DeviceCap
            {
                VERTRES = 10,
                DESKTOPVERTRES = 117,
            }

            public static float getScalingFactor()
            {
                IntPtr desktop = GetDC(GetDesktopWindow());
                int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
                int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES);

                float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
                USER32.ReleaseDC(GetDesktopWindow(), desktop);
                return ScreenScalingFactor; // 1.25 = 125%
            }
        }
    }
}

Como você pode ver, a diferença é que o WPF oferece uma opção nativa, enquanto que no Win Forms temos que lidar com interoperabilidade na API do Windows.

Um pequeno exemplo de uso para o WPF:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPF_Control_to_Image
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnRender_Click(object sender, RoutedEventArgs e)
        {
            BitmapSource bmp = VisualToBitmap.Render(MainBorder);
            Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
            dialog.Filter = "Imagem PNG(*.png)|*.png";
            if(dialog.ShowDialog() == true)
            {
                System.Windows.Media.Imaging.PngBitmapEncoder encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(bmp));
                System.IO.FileStream fs = new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Create);
                encoder.Save(fs);
                fs.Close();
            }
        }
    }
}

E um para o Win Forms:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace HWND_To_Image
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRender_Click(object sender, EventArgs e)
        {
            Bitmap bmp = HandleToBitmap.RenderClientArea(this.Handle);
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "Imagem PNG (*.png)|*.png";
            dialog.AddExtension = true;
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                bmp.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png);
            }
        }

    }
}

E isso é tudo. Vou disponibilizar o download da Solution com os exemplos para Win Forms e WPF:

Control to Image.zip

Até a próxima, pessoal!!!