Při snaze kreslit grafické objekty na celou obrazovku PDA, a ne pouze na vlastní formulář, se můžete velmi často setkat s dotazem, jak zajistím, že předchozí nakreslené dílko, rozprostřené většinou přes vícero formulářů a mimo naši přímou kontrolu, smažu před vykreslením dalšího dílka. Následující příklad je reakcí na takový dotaz. Za “celou obrazovkou” budeme v článku považovat grafický kontext vrácený voláním metody GetDC s argumentem NULL. Po celé obrazovce PDA je postupně vykreslován kruh (s velkou fantazií míč) pohybující se z levé strany displeje na pravou. Před vykreslením “míče” na další pozici musí být míč vykreslený v předcházejícím kroku smazán. Následující kód je ukázkou použití “brutální síly”, protože žádné slečinkovské, sexy ani elegantní konstrukce s překreslováním pouze části obrazovky k výsledku nevedly. Hlavní trik, ke kterému jsem dospěl po sundání bílých vývojářských rukaviček, spočívá v rekurzivním vynucení si překreslení všech oken v metodě DoWork s využitím přímého volání mnoha nativních API funkcí, které mají paradoxně v aplikacích cílených na Microsoftem macešsky spravovaný a rozvíjený Compact .Net Framework (i ve verzi. 3.5) stále privilegovanou pozici.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace RedrawScreenTest
{
partial class Form1 : Form
{
#region constants
public const int POSITION_INCREMENT = 10;
public const int MAX_POSITION = 200;
public const int MAX_THREAD_WAIT = 5000;
public const int CIRCLE_RADIUS = 30;
#endregion constants
#region delegates
public delegate void InvokeDelegate();
#endregion delegates
#region Properties
public IntPtr WindowsHDC { get; set; }
public Thread Worker { get; set; }
public bool ShouldWork { get; set; }
public bool IsWorking { get; set; }
#endregion Properties
#region Constructors
public Form1()
{
InitializeComponent();
WindowsHDC = IntPtr.Zero;
ShouldWork = false;
IsWorking = false;
}
#endregion Constructors
#region methods
public void DoWork()
{
IsWorking = true;
int x = 0;
int y = 0;
bool firstDraw = true;
while (ShouldWork)
{
WindowsHDC = ApiWrapper.GetDC(IntPtr.Zero);
if (WindowsHDC == IntPtr.Zero)
{
break;
}
if (!firstDraw)
{
Invoke((InvokeDelegate) (() =>
{
IntPtr hwnd = ApiWrapper.GetForegroundWindow();
hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDFIRST);
while (hwnd != IntPtr.Zero)
{
ApiWrapper.RedrawWindow(hwnd, IntPtr.Zero, IntPtr.Zero,
ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);
EnumChild(hwnd, 0);
hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);
}
}));
}
else
{
firstDraw = false;
}
using (Graphics g = Graphics.FromHdc(WindowsHDC))
using (Brush b = new SolidBrush(Color.Yellow))
{
g.FillEllipse(b, new Rectangle(x, y, CIRCLE_RADIUS, CIRCLE_RADIUS));
}
ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
Thread.Sleep(1000);
x += POSITION_INCREMENT;
y += POSITION_INCREMENT;
if (x > MAX_POSITION || y > MAX_POSITION)
{
x = 0;
y = 0;
}
}
IsWorking = false;
}
private void EnumChild(IntPtr hwnd, int level)
{
if ((hwnd == IntPtr.Zero))
{
return;
}
IntPtr childHwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_CHILD);
while (childHwnd != IntPtr.Zero)
{
EnumChild(childHwnd, level + 1);
ApiWrapper.RedrawWindow(childHwnd, IntPtr.Zero, IntPtr.Zero,
ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);
childHwnd = ApiWrapper.GetWindow(childHwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
buttonStop_Click(this, new EventArgs());
if (WindowsHDC != IntPtr.Zero)
{
ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
if (IsWorking)
{
return;
}
ShouldWork = true;
Worker = new Thread(DoWork);
Worker.Start();
}
private void buttonStop_Click(object sender, EventArgs e)
{
if (!IsWorking)
{
return;
}
ShouldWork = false;
Worker.Join(MAX_THREAD_WAIT);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
}
#endregion methods
}
}
Zde jsou potřebné deklarace API funkcí, konstant a struktur
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace RedrawScreenTest
{
class ApiWrapper
{
[DllImport("coredll.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate,
IntPtr hrgnUpdate, uint flags);
public const int RDW_INVALIDATE = 0x0001;
const int RDW_INTERNALPAINT = 0x0002;
public const int RDW_ERASE = 0x0004;
const int RDW_VALIDATE = 0x0008;
const int RDW_NOINTERNALPAINT = 0x0010;
const int RDW_NOERASE = 0x0020;
const int RDW_NOCHILDREN = 0x0040;
public const int RDW_ALLCHILDREN = 0x0080;
const int RDW_UPDATENOW = 0x0100;
const int RDW_ERASENOW = 0x0200;
const int RDW_FRAME = 0x0400;
const int RDW_NOFRAME = 0x0800;
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
public enum GWConstants
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6
}
[DllImport("coredll.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("coredll.dll", EntryPoint = "GetWindowDC")]
public static extern IntPtr GetDC(IntPtr ptr);
[DllImport("coredll.dll", EntryPoint = "ReleaseDC")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("coredll.dll", EntryPoint = "GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
[DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
public static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
public static extern int SendMessage(IntPtr hWnd, uint msg, int wparam, int lparam);
[DllImport("coredll.dll")]
public extern static void InvalidateRect(IntPtr handle, Rectangle dummy, bool erase);
[DllImport("coredll.dll", EntryPoint = "FindWindowW", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
}