Win32

Criando um ícone personalizado para seu Aplicativo Windows – DICA

Icon Pro

Criando um ícone a partir de um arquivo SVG

GitHub – HerbertLausmann/HL.IconPro

Fala galera, beleza?!

Neste post eu venho lhes (re)apresentar minha ferramenta open source para criação de ícones para o Windows.

Como vocês podem ver acima, no GIF, é bem simples!! Basta selecionar uma imagem (PNG ou SVG), selecionar os tamanhos que irão para dentro do ícone, e pronto! Só salvar!

(mais…)

Anúncios

Windows Explorer: Abrir Explorer com arquivo/pasta selecionado – C#

Falaí galera!!!

Fiquei um tempinho sem postar.. Tá ok, um tempão!

Mas eu não ia deixar o blog fazer aniversário de inatividade, então resolvi postar alguma coisa..

O assunto de hoje é o Windows Explorer. Vou mostrar como abrir o Windows Explorer selecionando algum arquivo ou pasta. Um exemplo de uso muito bom é um gerenciador de downloads que muitos já devem ter experimentado. Quando termina o download, normalmente ele lhe dá a opção de Abrir Pasta, que vai abrir a pasta onde está o arquivo que você baixou. E, de quebra, ele seleciona o arquivo para facilitar a localização do mesmo pelo usuário final.

Executar essa tarefa em código é bem simples. Eis um exemplo:

        public void OpenAndSelectPath(string path)
        {
            bool isfile = System.IO.File.Exists(path);
            if (isfile)
            {
                string argument = @"/select, " + path;
                System.Diagnostics.Process.Start("explorer.exe", argument);
            }
            else
            {
                bool isfolder = System.IO.Directory.Exists(path);
                if(isfolder)
                {
                    string argument = @"/select, " + path;
                    System.Diagnostics.Process.Start("explorer.exe", argument);
                }
            }
        }

Essas poucas linhas acima farão este trabalho. Basicamente, o que fazemos é rodar o processo explorer.exe (Corresponder ao Windows Explorer) informando como argumento a instrução -select e o caminho do arquivo/pasta a ser selecionado. Segue um breve exemplo de uso:

       OpenAndSelectPath(@"H:\Biblioteca\Documentos\arquivo.txt");

Simples e prático! 🙂

Espero voltar em breve com novos posts!

Kernel: Obtendo o caminho do executável de um Processo – C#

Pode parecer tarefa fácil, mas não é. O fato é que, obter o caminho do executável de um processo pode acabar tendo um destino trágico se você não estiver com sorte.

A maneira tradicional de fazer isso é utilizando a propriedade Process.MainModule.FileName.

Mas há um grande porém! Se o seu processo está executando em x64 (Processo 64 bits) e o outro processo, que você deseja obter o caminho, está executando em x86 (Processo 32 bits) você receberá a belíssima exceção abaixo:

Cross Plat Exception

A situação inversa também resulta neste exceção.

Esse é um problema cada vez mais fácil de ocorrer devido à maior adoção de sistemas operacionais x64.

Mas, contudo, todavia, começando com a versão Vista, o Windows possui uma função no Kernel que nos permite contornar este problema. Ela se chama QueryFullProcessImageName. Essa rotina não é afetada pela situação descrita acima.

Dessa forma, com algumas linhas de código, podemos implementar um método de extensão para a classe Process que irá facilitar as coisas:

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

namespace System.Diagnostics
{
    public static class ProcessExtensions
    {
        /// <summary>
        /// Returns the process executable full path
        /// </summary>
        /// <param name="Process">The process</param>
        /// <returns>A string containing the process executable path</returns>
        public static string GetProcessPath(this Process Process)
        {
            int capacity = 1024;
            StringBuilder sb = new StringBuilder(capacity);

            IntPtr handle = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, Process.Id);

            QueryFullProcessImageName(handle, 0, sb, ref capacity);

            string fullPath = sb.ToString(0, capacity);
            CloseHandle(handle);
            return fullPath;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize);

        [DllImport("kernel32.dll")]
        private static extern IntPtr OpenProcess(
             ProcessAccessFlags processAccess,
             bool bInheritHandle,
             int processId
        );

        [Flags]
        private enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000
        }
    }
}

Após implementar o código acima, você poderá fazer coisas assim no seu código:

                foreach (Process pr in System.IO.Kernel.GetProcessesLockingFile(dialog.FileName))
                {
                    DataRow r = dt.NewRow();
                    r[0] = pr.ProcessName;
                    r[1] = pr.Id;
                    r[2] = pr.GetProcessPath(); // Uso da função
                    dt.Rows.Add(r);
                }

Como diria um amigo meu: Nice and flexible.

Kernel: Obtendo os processos que estão usando um arquivo – C#

Pode parecer tarefa fácil, mas não é. O fato é que, obter o processo ou os processos que estão usando um arquivo/pasta é uma tarefa com uma pitada de complexidade em C++ e realmente complexa em C#/VB devido às diversas importações de funções necessárias.

No entanto, existe um utilitário gratuito que lhe permite descobrir essas coisas de forma super simples. Falo do Handle:

O Handle é um Console Application que faz todo o trabalho árduo; Você apenas especifica o caminho para um arquivo ou pasta e ele lhe retorna os processos que o estão usando.

Sendo assim, vou mostrar como usar este utilitário. Primeiro, obviamente faça o Download dele no link acima. Descompacte o arquivo zip e copie o arquivo Handle.exe para a pasta da sua aplicação. Então o poderá colocar em funcionamento o código abaixo:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace System.IO
{
    public static class Kernel
    {
        /// <summary>
        /// Returns all the processes locking a file/folder
        /// </summary>
        /// <param name="path">The full path to file/folder to inspect</param>
        /// <returns>A IEnumerable containing all the processes locking the file/folder</returns>
        public static IEnumerable<Process> GetProcessesLockingFile(string path)
        {
            string currentDirectory = System.IO.Path.GetDirectoryName(new Uri(
                System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
            string handlePath = currentDirectory + "\\handle.exe";
            ProcessStartInfo handleStarting = new ProcessStartInfo(handlePath);
            handleStarting.UseShellExecute = false;
            handleStarting.RedirectStandardOutput = true;
            handleStarting.Arguments = "\"" + path + "\" /accepteula";
            handleStarting.Verb = "runas";
            handleStarting.CreateNoWindow = true;
            Process handleProcess = Process.Start(handleStarting);
            handleProcess.WaitForExit();

            string outputTool = handleProcess.StandardOutput.ReadToEnd();

            string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
            foreach (Match match in Regex.Matches(outputTool, matchPattern))
            {
                Process pr = null;
                try
                {
                    pr = Process.GetProcessById(int.Parse(match.Value));
                }
                catch { }
                if (pr != null)
                    yield return pr;
            }
            yield break;
        }
    }
}

Esse código, basicamente, executa o utilitário e faz a leitura do resultado convertendo-o em um Enumerable de processos que estão usando o arquivo/pasta.

Mas, devo salientar algo muito importante:

O fato de um programa abrir um arquivo não quer dizer que ele esteja usando este arquivo. Se você abrir uma música no Windows Media Player ele irá carregar o conteúdo do arquivo para a memória, fechar este arquivo e então reproduzir. No fim das contas o código não irá detectar nenhum programa usando este arquivo de música, pois, realmente, nenhum está utilizando.

O mesmo pode acontecer com qualquer arquivo, pasta e software…

E, de brinde, vou disponibilizar para download um projeto de um programa pequeno para inspecionar arquivos e pastas para saber se estão sendo usados:

File Locker.zip

That’s all folks!

Fonte: c# – How do I find out which process is locking a file using .NET? – Stack Overflow

Win32: Carregando um aplicativo externo dentro de um Panel – C# Win Forms

Fala aí galera!

Bom, hoje quero falar de algo um tanto interessante, e bem útil em casos bem raros. Trata-se de carregar a janela de uma outra aplicação dentro da sua aplicação, como se a janela externa fosse parte do seu aplicativo, quando na verdade está sendo executada em um processo completamente separado.

Abaixo, um print do que falo:

Application Hoster

No caso acima, eu carreguei o Paint dentro do Panel da janela do meu aplicativo.

Eu já utilizei isso de forma prática no passado. Foi uma vez que eu criei um aplicativo para baixar charges animadas do site animatunes.com. As charges eram baixadas pelo aplicativo (no formato swf, flash) apenas usando a url da página. O aplicativo incluía um player de vídeo para rodar as charges. No entanto, naquela época, o componente SWF Player COM consumia muito processamento, e as vezes até travava. Então eu separei a aplicação em duas, sendo a primeira a aplicação em si,  e a outra apenas o componente SWF Player. Quando em execução, o projeto carregava o player do processo separado dentro de um panel, como se tudo fosse parte de um executável só. Caso o player travasse, era só reiniciar o processo do player e pronto. Foi a maneira que eu consegui para manter o projeto estável.

Mais ou menos o mesmo conceito é usado no Google Chrome, onde cada guia é executada em um processo separado. Se uma guia travar, você pode finaliza-la sem afetar em nada a aplicação.

A técnica acima, combinada com IPC (Inter Process Communication – Comunicação Inter Processual) lhe dá o poder de criar aplicações multi processos.

Não vou abordar o IPC neste post, mas pretendo faze-lo no futuro.

Segue o link para baixar o projeto do Application Hoster:

Application Hoster.zip – 60KB

O código está comentando para melhor entendimento.

Até a próxima.

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!!!

Win Forms: Mover form sem borda no C#

Código bem simples para mover um Form no C#. Inclusive é a melhor forma, mais leve e sem delays:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

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

        public const int WM_NCLBUTTONDOWN = 0xA1;
        public const int HT_CAPTION = 0x2;
        public const int WM_LBUTTONDOWN = 0x0201;

        [DllImportAttribute("user32.dll")]
        public static extern int SendMessage(IntPtr hWnd,
                         int Msg, int wParam, int lParam);
        [DllImportAttribute("user32.dll")]
        public static extern bool ReleaseCapture();

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (m.Msg == WM_LBUTTONDOWN)
            {
                ReleaseCapture();
                SendMessage(this.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
            }
        }

    }
}

Se você programa em VB.NET, eu tenho um post sobre isso para você:

Movendo um Form sem borda (FormBorderStyle = None): A forma correta – VB.NET