Handle paint exceptions, showing a red cross like .NET does. Fixes bug 694908.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / NativeWindow.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004-2008 Novell, Inc.
21 //
22 // Authors:
23 //      Peter Dennis Bartok     pbartok@novell.com
24 //      Ivan N. Zlatev          <contact@i-nz.net>
25 //
26
27
28 // COMPLETE
29
30 //#define ExternalExceptionHandler
31
32 using System.Runtime.Remoting;
33 using System.Runtime.InteropServices;
34 using System.Runtime.CompilerServices;
35 using System.Threading;
36 using System.Collections;
37 using System.Diagnostics;
38 using System.Drawing;
39
40 namespace System.Windows.Forms
41 {
42         public class NativeWindow : MarshalByRefObject, IWin32Window
43         {
44                 IntPtr window_handle = IntPtr.Zero;
45                 static Hashtable window_collection = new Hashtable();
46
47                 [ThreadStatic]
48                 static NativeWindow WindowCreating;
49
50                 #region Public Constructors
51                 public NativeWindow()
52                 {
53                         window_handle=IntPtr.Zero;
54                 }
55                 #endregion      // Public Constructors
56
57                 #region Public Instance Properties
58                 public IntPtr Handle {
59                         get {
60                                 return window_handle;
61                         }
62                 }
63                 #endregion      // Public Instance Properties
64
65                 #region Public Static Methods
66                 public static NativeWindow FromHandle(IntPtr handle)
67                 {
68                         return FindFirstInTable (handle);
69                 }
70                 #endregion      // Public Static Methods
71
72                 #region Private and Internal Methods
73                 internal void InvalidateHandle()
74                 {
75                         RemoveFromTable (this);
76                         window_handle = IntPtr.Zero;
77                 }
78                 #endregion
79
80                 #region Public Instance Methods
81                 public void AssignHandle(IntPtr handle)
82                 {
83                         RemoveFromTable (this);
84                         window_handle = handle;
85                         AddToTable (this);
86                         OnHandleChange();
87                 }
88
89                 private static void AddToTable (NativeWindow window)
90                 {
91                         IntPtr handle = window.Handle;
92                         if (handle == IntPtr.Zero)
93                                 return;
94
95                         lock (window_collection) {
96                                 object current = window_collection[handle];
97                                 if (current == null) {
98                                         window_collection.Add (handle, window);
99                                 } else {
100                                         NativeWindow currentWindow = current as NativeWindow;
101                                         if (currentWindow != null) {
102                                                 if (currentWindow != window) {
103                                                         ArrayList windows = new ArrayList ();
104                                                         windows.Add (currentWindow);
105                                                         windows.Add (window);
106                                                         window_collection[handle] = windows;
107                                                 }
108                                         } else { // list of windows
109                                                 ArrayList windows = (ArrayList) window_collection[handle];
110                                                 if (!windows.Contains (window))
111                                                         windows.Add (window);
112                                         }
113                                 }
114                         }
115                 }
116
117                 private static void RemoveFromTable (NativeWindow window)
118                 {
119                         IntPtr handle = window.Handle;
120                         if (handle == IntPtr.Zero)
121                                 return;
122
123                         lock (window_collection) {
124                                 object current = window_collection[handle];
125                                 if (current != null) {
126                                         NativeWindow currentWindow = current as NativeWindow;
127                                         if (currentWindow != null) {
128                                                 window_collection.Remove (handle);
129                                         } else { // list of windows
130                                                 ArrayList windows = (ArrayList) window_collection[handle];
131                                                 windows.Remove (window);
132                                                 if (windows.Count == 0)
133                                                         window_collection.Remove (handle);
134                                                 else if (windows.Count == 1)
135                                                         window_collection[handle] = windows[0];
136                                         }
137                                 }
138                         }
139                 }
140
141                 private static NativeWindow FindFirstInTable (IntPtr handle)
142                 {
143                         if (handle == IntPtr.Zero)
144                                 return null;
145
146                         NativeWindow window = null;
147                         lock (window_collection) {
148                                 object current = window_collection[handle];
149                                 if (current != null) {
150                                         window = current as NativeWindow;
151                                         if (window == null) {
152                                                 ArrayList windows = (ArrayList) current;
153                                                 if (windows.Count > 0)
154                                                         window = (NativeWindow) windows[0];
155                                         }
156                                 }
157                         }
158                         return window;
159                 }
160
161                 public virtual void CreateHandle(CreateParams cp)
162                 {
163                         if (cp != null) {
164                                 WindowCreating = this;
165                                 window_handle=XplatUI.CreateWindow(cp);
166                                 WindowCreating = null;
167
168                                 if (window_handle != IntPtr.Zero)
169                                         AddToTable (this);
170                         }
171
172                 }
173
174                 public void DefWndProc(ref Message m)
175                 {
176                         m.Result=XplatUI.DefWndProc(ref m);
177                 }
178
179                 public virtual void DestroyHandle()
180                 {
181                         if (window_handle != IntPtr.Zero) {
182                                 XplatUI.DestroyWindow(window_handle);
183                         }
184                 }
185
186                 public virtual void ReleaseHandle()
187                 {
188                         RemoveFromTable (this);
189                         window_handle=IntPtr.Zero;
190                         OnHandleChange();
191                 }
192
193                 #endregion      // Public Instance Methods
194
195                 #region Protected Instance Methods
196                 ~NativeWindow()
197                 {
198                 }
199
200                 protected virtual void OnHandleChange()
201                 {
202                 }
203
204                 protected virtual void OnThreadException(Exception e)
205                 {
206                         Application.OnThreadException(e);
207                 }
208
209                 protected virtual void WndProc(ref Message m)
210                 {
211                         DefWndProc(ref m);
212                 }
213
214                 internal static IntPtr WndProc(IntPtr hWnd, Msg msg, IntPtr wParam, IntPtr lParam)
215                 {
216                         IntPtr result = IntPtr.Zero;
217                         Message m = new Message();
218                         m.HWnd = hWnd;
219                         m.Msg = (int)msg;
220                         m.WParam = wParam;
221                         m.LParam = lParam;
222                         m.Result = IntPtr.Zero;
223                                         
224 #if debug
225                         Console.WriteLine("NativeWindow.cs ({0}, {1}, {2}, {3}): result {4}", hWnd, msg, wParam, lParam, m.Result);
226 #endif
227                         NativeWindow window = null;
228                         
229                         try {
230                         object current = null;
231                         lock (window_collection) {
232                                 current = window_collection[hWnd];
233                         }
234
235                         window = current as NativeWindow;
236                         if (current == null)
237                                 window = EnsureCreated (window, hWnd);
238
239                         if (window != null) {
240                                 window.WndProc (ref m);
241                                 result = m.Result;
242                         } else if (current is ArrayList) {
243                                 ArrayList windows = (ArrayList) current;
244                                 lock (windows) {
245                                         if (windows.Count > 0) {
246                                                 window = EnsureCreated ((NativeWindow)windows[0], hWnd);
247                                                 window.WndProc (ref m);
248                                                 // the first one is the control's one. all others are synthetic,
249                                                 // so we want only the result from the control
250                                                 result = m.Result;
251                                                 for (int i=1; i < windows.Count; i++)
252                                                         ((NativeWindow)windows[i]).WndProc (ref m);
253                                         }
254                                 }
255                         } else {
256                                 result = XplatUI.DefWndProc (ref m);
257                         }
258                         }
259                         catch (Exception ex) {
260 #if !ExternalExceptionHandler
261                                 if (window != null) {
262                                         if (msg == Msg.WM_PAINT && window is Control.ControlNativeWindow) {
263                                                 // Replace control with a red cross
264                                                 var control = ((Control.ControlNativeWindow)window).Owner;
265                                                 control.Hide ();
266                                                 var redCross = new Control (control.Parent, string.Empty);
267                                                 redCross.BackColor = Color.White;
268                                                 redCross.ForeColor = Color.Red;
269                                                 redCross.Bounds = control.Bounds;
270                                                 redCross.Paint += HandleRedCrossPaint;
271                                         }
272                                         window.OnThreadException (ex);
273                                 }
274 #else
275                                 throw;
276 #endif
277                         }
278                         #if debug
279                                 Console.WriteLine("NativeWindow.cs: Message {0}, result {1}", msg, m.Result);
280                         #endif
281
282                         return result;
283                 }
284
285                 private static void HandleRedCrossPaint (object sender, PaintEventArgs e)
286                 {
287                         var control = sender as Control;
288                         using (var pen = new Pen (control.ForeColor, 2)) {
289                                 var paintRect = control.DisplayRectangle;
290                                 e.Graphics.DrawRectangle (pen, paintRect.Left + 1,
291                                         paintRect.Top + 1, paintRect.Width - 1, paintRect.Height - 1);
292                                 // NOTE: .NET's drawing of the red cross seems to have a bug
293                                 // that draws the bottom and right of the rectangle only 1 pixel
294                                 // wide. We would get a nicer rectangle using the following code,
295                                 // but that runs into a problem with libgdiplus.
296                                 //var paintRect = control.DisplayRectangle;
297                                 //paintRect.Inflate (-1, -1);
298                                 //e.Graphics.DrawRectangle (pen, paintRect);
299                                 e.Graphics.DrawLine (pen, paintRect.Location,
300                                         paintRect.Location + paintRect.Size);
301                                 e.Graphics.DrawLine (pen, new Point (paintRect.Left, paintRect.Bottom),
302                                         new Point (paintRect.Right, paintRect.Top));
303                         }
304                 }
305
306                 private static NativeWindow EnsureCreated (NativeWindow window, IntPtr hWnd)
307                 {
308                         // we need to do this AssignHandle here instead of relying on
309                         // Control.WndProc to do it, because subclasses can override
310                         // WndProc, install their own WM_CREATE block, and look at
311                         // this.Handle, and it needs to be set.  Otherwise, we end up
312                         // recursively creating windows and emitting WM_CREATE.
313                         if (window == null && WindowCreating != null) {
314                                 window = WindowCreating;
315                                 WindowCreating = null;
316                                 if (window.Handle == IntPtr.Zero)
317                                         window.AssignHandle (hWnd);
318                         }
319                         return window;
320                 }
321                 #endregion      // Protected Instance Methods
322         }
323 }